【Silverlight】利用IsolatedStorageFile实现客户端缓存
为将一套系统局部改版为BS应用,最近在琢磨Silverlight,发现这玩意可能真能带来一场革命,对于程序员来说比Flash好的还真不是一星半点。
废话就不说了,来点实在的。我们有些数据,特别是些不是经常变的数据往往需要进行缓存,以往在asp.net的时候使用page的cache属性,其实那还是存放在服务器端,对服务器压力比较大,这下好了silverlight提供了IsolatedStorageFile,而且可以申请无限量空间,当然是要人家客户端同意了,实现也很简单
ApplyStorageSpace
/// <summary>
/// 在客户端申请存储空间
/// </summary>
/// <param name="size">空间大小(KB)</param>
/// <returns></returns>
public static bool ApplyStorageSpace(double size)
{
try
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
Int64 newQuotaSize = Convert.ToInt64(size * 1024); //换算为字节
Int64 curAvail = store.AvailableFreeSpace;
if (curAvail < newQuotaSize)
{
if (!store.IncreaseQuotaTo(newQuotaSize))
return false;
else
return true;
}
else
{
return true;
}
}
}
catch (IsolatedStorageException ex)
{
throw ex;
}
}
/// <summary>
/// 在客户端申请存储空间
/// </summary>
/// <param name="size">空间大小(KB)</param>
/// <returns></returns>
public static bool ApplyStorageSpace(double size)
{
try
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
Int64 newQuotaSize = Convert.ToInt64(size * 1024); //换算为字节
Int64 curAvail = store.AvailableFreeSpace;
if (curAvail < newQuotaSize)
{
if (!store.IncreaseQuotaTo(newQuotaSize))
return false;
else
return true;
}
else
{
return true;
}
}
}
catch (IsolatedStorageException ex)
{
throw ex;
}
}
看到这个东西自然而然就想到了,用来做缓存和离线数据存储肯定能成,于是找了下老外的做法(silverlight还是老外用的多啊)找个个不错的类,简单改了改就很好用了
ClientStorage
/// <summary>
/// Client Storage class for storing objects in IsolatedStorage
/// </summary>
public class ClientStorage
{
#region Constants
const string ISOLATED_KEY_FILE_NAME = "KeyNames.txt";
const string KEY_OBJECT_FILE = "object.xml";
#endregion
#region Private Static Fields
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
Dictionary<string, TypeAndValue> keysNTypes;
#endregion
#region CTOR
/// <summary>
/// private CTOR to initialize the class to make it singleton
/// </summary>
private ClientStorage()
{
keysNTypes = new Dictionary<string, TypeAndValue>();
if (FileExists(ISOLATED_KEY_FILE_NAME))
{
ReadKeys(isoStore);
}
}
/// <summary>
/// Nested class for lazy initialization.
/// </summary>
class NestedClientStorage
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static NestedClientStorage()
{
}
internal static readonly ClientStorage Instance = new ClientStorage();
}
#endregion
#region Private Helper Methods
private void ReadKeys(IsolatedStorageFile isoStore)
{
IsolatedStorageFileStream iStream = new IsolatedStorageFileStream(ISOLATED_KEY_FILE_NAME,
FileMode.Open, isoStore);
DataContractSerializer serializer = new DataContractSerializer(keysNTypes.GetType());
keysNTypes = serializer.ReadObject(iStream) as Dictionary<string, TypeAndValue>;
iStream.Close();
}
private void AddKey(string key, object value)
{
if (!keysNTypes.ContainsKey(key))
keysNTypes.Add(key, new TypeAndValue());
keysNTypes[key].TypeofObject = value.GetType();
keysNTypes[key].StoredObject = value;
WriteKeyFile();
}
private void WriteKeyFile()
{
using (IsolatedStorageFileStream oStream = new IsolatedStorageFileStream(ISOLATED_KEY_FILE_NAME,
FileMode.Create, isoStore))
{
DataContractSerializer serializer = new DataContractSerializer(keysNTypes.GetType());
serializer.WriteObject(oStream, keysNTypes);
oStream.Close();
}
}
private object Retreive(string key)
{
object value = null;
if (FileExists(key + KEY_OBJECT_FILE) && keysNTypes.ContainsKey(key))
{
if (keysNTypes[key].StoredObject == null)
{
try
{
using (IsolatedStorageFileStream iStream = new IsolatedStorageFileStream(key + KEY_OBJECT_FILE, FileMode.OpenOrCreate, isoStore))
{
if (iStream != null)
{
try
{
DataContractSerializer serializer = new DataContractSerializer(keysNTypes[key].TypeofObject);
value = serializer.ReadObject(iStream);
}
catch (Exception)
{
// Do nothing simply retrun null
}
keysNTypes[key].StoredObject = value;
iStream.Close();
}
}
}
catch (FileNotFoundException)
{
throw new KeyNotFoundException();
}
}
else
{
value = keysNTypes[key].StoredObject;
}
}
return value;
}
private void AddOrUpdate(string key, object value)
{
try
{
IsolatedStorageFileStream oStream = new IsolatedStorageFileStream(key + KEY_OBJECT_FILE,
FileMode.Create, isoStore);
DataContractSerializer serializer = new DataContractSerializer(value.GetType());
serializer.WriteObject(oStream, value);
oStream.Close();
}
catch (IsolatedStorageException)
{
if (ApplyStorageSpace(1024 * 1024 * 1024))
{
AddOrUpdate(key, value);
}
}
}
private void Add(string key, object value, bool throwErrorOnDuplicate)
{
if (keysNTypes.ContainsKey(key) && throwErrorOnDuplicate)
{
throw new System.Exception("Duplicate key provided.");
}
else
{
AddKey(key, value);
AddOrUpdate(key, value);
}
}
private bool FileExists(string fileName)
{
return isoStore.FileExists(fileName);
}
#endregion
#region Public Methods
/// <summary>
/// Public static property to get the instance of ClientStorage which is a singleton class
/// </summary>
public static ClientStorage Instance
{
get
{
return NestedClientStorage.Instance;
}
}
/// <summary>
/// Adds a key/value to the storage device.
/// </summary>
/// <param name="key">Key to identify the object</param>
/// <param name="versionNumber">Version Number</param>
/// <param name="value">Value as object</param>
public void Add(string key, object value)
{
Add(key, value, true);
}
/// <summary>
/// Remove a element from the Isolated Storage
/// </summary>
/// <param name="key">key</param>
public void Remove(string key)
{
keysNTypes.Remove(key);
WriteKeyFile();
if (FileExists(key + KEY_OBJECT_FILE))
{
isoStore.DeleteFile(key + KEY_OBJECT_FILE);
}
}
/// <summary>
/// Indexer for CLientStorage
/// </summary>
/// <param name="key">Key</param>
/// <param name="versionNumber"> Version Number</param>
/// <returns>returns the object on the basis of key</returns>
public object this[string key]
{
get
{
return Retreive(key);
}
set
{
Add(key, value, false);
}
}
/// <summary>
/// 在客户端申请存储空间
/// </summary>
/// <param name="size">空间大小(KB)</param>
/// <returns></returns>
public static bool ApplyStorageSpace(double size)
{
try
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
Int64 newQuotaSize = Convert.ToInt64(size * 1024); //换算为字节
Int64 curAvail = store.AvailableFreeSpace;
if (curAvail < newQuotaSize)
{
if (!store.IncreaseQuotaTo(newQuotaSize))
return false;
else
return true;
}
else
{
return true;
}
}
}
catch (IsolatedStorageException ex)
{
throw ex;
}
}
#endregion
#region TypeAndValue
[DataContract]
public class TypeAndValue
{
public TypeAndValue()
{
}
public Type TypeofObject { get; set; }
public object StoredObject { get; set; }
private string fullyQualifiedTypeName;
[DataMember]
public string FullyQualifiedTypeName
{
get
{
if (fullyQualifiedTypeName == null)
{
fullyQualifiedTypeName = TypeofObject.AssemblyQualifiedName;
}
return fullyQualifiedTypeName;
}
set
{
fullyQualifiedTypeName = value;
TypeofObject = Type.GetType(fullyQualifiedTypeName);
}
}
}
#endregion
}
/// <summary>
/// Client Storage class for storing objects in IsolatedStorage
/// </summary>
public class ClientStorage
{
#region Constants
const string ISOLATED_KEY_FILE_NAME = "KeyNames.txt";
const string KEY_OBJECT_FILE = "object.xml";
#endregion
#region Private Static Fields
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
Dictionary<string, TypeAndValue> keysNTypes;
#endregion
#region CTOR
/// <summary>
/// private CTOR to initialize the class to make it singleton
/// </summary>
private ClientStorage()
{
keysNTypes = new Dictionary<string, TypeAndValue>();
if (FileExists(ISOLATED_KEY_FILE_NAME))
{
ReadKeys(isoStore);
}
}
/// <summary>
/// Nested class for lazy initialization.
/// </summary>
class NestedClientStorage
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static NestedClientStorage()
{
}
internal static readonly ClientStorage Instance = new ClientStorage();
}
#endregion
#region Private Helper Methods
private void ReadKeys(IsolatedStorageFile isoStore)
{
IsolatedStorageFileStream iStream = new IsolatedStorageFileStream(ISOLATED_KEY_FILE_NAME,
FileMode.Open, isoStore);
DataContractSerializer serializer = new DataContractSerializer(keysNTypes.GetType());
keysNTypes = serializer.ReadObject(iStream) as Dictionary<string, TypeAndValue>;
iStream.Close();
}
private void AddKey(string key, object value)
{
if (!keysNTypes.ContainsKey(key))
keysNTypes.Add(key, new TypeAndValue());
keysNTypes[key].TypeofObject = value.GetType();
keysNTypes[key].StoredObject = value;
WriteKeyFile();
}
private void WriteKeyFile()
{
using (IsolatedStorageFileStream oStream = new IsolatedStorageFileStream(ISOLATED_KEY_FILE_NAME,
FileMode.Create, isoStore))
{
DataContractSerializer serializer = new DataContractSerializer(keysNTypes.GetType());
serializer.WriteObject(oStream, keysNTypes);
oStream.Close();
}
}
private object Retreive(string key)
{
object value = null;
if (FileExists(key + KEY_OBJECT_FILE) && keysNTypes.ContainsKey(key))
{
if (keysNTypes[key].StoredObject == null)
{
try
{
using (IsolatedStorageFileStream iStream = new IsolatedStorageFileStream(key + KEY_OBJECT_FILE, FileMode.OpenOrCreate, isoStore))
{
if (iStream != null)
{
try
{
DataContractSerializer serializer = new DataContractSerializer(keysNTypes[key].TypeofObject);
value = serializer.ReadObject(iStream);
}
catch (Exception)
{
// Do nothing simply retrun null
}
keysNTypes[key].StoredObject = value;
iStream.Close();
}
}
}
catch (FileNotFoundException)
{
throw new KeyNotFoundException();
}
}
else
{
value = keysNTypes[key].StoredObject;
}
}
return value;
}
private void AddOrUpdate(string key, object value)
{
try
{
IsolatedStorageFileStream oStream = new IsolatedStorageFileStream(key + KEY_OBJECT_FILE,
FileMode.Create, isoStore);
DataContractSerializer serializer = new DataContractSerializer(value.GetType());
serializer.WriteObject(oStream, value);
oStream.Close();
}
catch (IsolatedStorageException)
{
if (ApplyStorageSpace(1024 * 1024 * 1024))
{
AddOrUpdate(key, value);
}
}
}
private void Add(string key, object value, bool throwErrorOnDuplicate)
{
if (keysNTypes.ContainsKey(key) && throwErrorOnDuplicate)
{
throw new System.Exception("Duplicate key provided.");
}
else
{
AddKey(key, value);
AddOrUpdate(key, value);
}
}
private bool FileExists(string fileName)
{
return isoStore.FileExists(fileName);
}
#endregion
#region Public Methods
/// <summary>
/// Public static property to get the instance of ClientStorage which is a singleton class
/// </summary>
public static ClientStorage Instance
{
get
{
return NestedClientStorage.Instance;
}
}
/// <summary>
/// Adds a key/value to the storage device.
/// </summary>
/// <param name="key">Key to identify the object</param>
/// <param name="versionNumber">Version Number</param>
/// <param name="value">Value as object</param>
public void Add(string key, object value)
{
Add(key, value, true);
}
/// <summary>
/// Remove a element from the Isolated Storage
/// </summary>
/// <param name="key">key</param>
public void Remove(string key)
{
keysNTypes.Remove(key);
WriteKeyFile();
if (FileExists(key + KEY_OBJECT_FILE))
{
isoStore.DeleteFile(key + KEY_OBJECT_FILE);
}
}
/// <summary>
/// Indexer for CLientStorage
/// </summary>
/// <param name="key">Key</param>
/// <param name="versionNumber"> Version Number</param>
/// <returns>returns the object on the basis of key</returns>
public object this[string key]
{
get
{
return Retreive(key);
}
set
{
Add(key, value, false);
}
}
/// <summary>
/// 在客户端申请存储空间
/// </summary>
/// <param name="size">空间大小(KB)</param>
/// <returns></returns>
public static bool ApplyStorageSpace(double size)
{
try
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
Int64 newQuotaSize = Convert.ToInt64(size * 1024); //换算为字节
Int64 curAvail = store.AvailableFreeSpace;
if (curAvail < newQuotaSize)
{
if (!store.IncreaseQuotaTo(newQuotaSize))
return false;
else
return true;
}
else
{
return true;
}
}
}
catch (IsolatedStorageException ex)
{
throw ex;
}
}
#endregion
#region TypeAndValue
[DataContract]
public class TypeAndValue
{
public TypeAndValue()
{
}
public Type TypeofObject { get; set; }
public object StoredObject { get; set; }
private string fullyQualifiedTypeName;
[DataMember]
public string FullyQualifiedTypeName
{
get
{
if (fullyQualifiedTypeName == null)
{
fullyQualifiedTypeName = TypeofObject.AssemblyQualifiedName;
}
return fullyQualifiedTypeName;
}
set
{
fullyQualifiedTypeName = value;
TypeofObject = Type.GetType(fullyQualifiedTypeName);
}
}
}
#endregion
}
本来用来做缓存这个就够用了,但为了能进一步使用方便,还是做了件多余的事就是在这个基础上再封了个Cache类这样用起来可能就更直观了
Cache
namespace SilverlightCache
{
public class Cache
{
Cache()
{
}
/// <summary>
/// Nested class for lazy initialization.
/// </summary>
class NestedCache
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static NestedCache()
{
}
internal static readonly Cache Instance = new Cache();
}
public static Cache Instance
{
get { return NestedCache.Instance; }
}
public object this[string key]
{
get { return ClientStorage.Instance[key]; }
set { ClientStorage.Instance[key] = value; }
}
public void Remove(string key)
{
ClientStorage.Instance.Remove(key);
}
}
}
namespace SilverlightCache
{
public class Cache
{
Cache()
{
}
/// <summary>
/// Nested class for lazy initialization.
/// </summary>
class NestedCache
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static NestedCache()
{
}
internal static readonly Cache Instance = new Cache();
}
public static Cache Instance
{
get { return NestedCache.Instance; }
}
public object this[string key]
{
get { return ClientStorage.Instance[key]; }
set { ClientStorage.Instance[key] = value; }
}
public void Remove(string key)
{
ClientStorage.Instance.Remove(key);
}
}
}
用法也超简单,例程假设的是从服务取数据,然后缓存到客户端,下次使用时不再访问服务即可,当然如果用户清理了IsolatedStorage就要重新取服务上的数据了
Usage
public partial class MainPage : UserControl
{
Service1Client client = new Service1Client();
const string KEY_CACHE = "key_cache";
public MainPage()
{
InitializeComponent();
client.DoWorkCompleted += new EventHandler<DoWorkCompletedEventArgs>(client_DoWorkCompleted);
}
void client_DoWorkCompleted(object sender, DoWorkCompletedEventArgs e)
{
Cache.Instance[KEY_CACHE] = e.Result;
Message("Service", "List's count:"+e.Result.Count().ToString());
}
private void btnTest_Click(object sender, RoutedEventArgs e)
{
if (Cache.Instance[KEY_CACHE] == null)
{
client.DoWorkAsync();
}
else
{
Message("Cache", "List's count:" + (Cache.Instance[KEY_CACHE] as ObservableCollection<Something>).Count().ToString());
}
}
void Message(string from, string msg)
{
MessageBox.Show(string.Format("Data from {0}, msg is {1}", from, msg));
}
}
public partial class MainPage : UserControl
{
Service1Client client = new Service1Client();
const string KEY_CACHE = "key_cache";
public MainPage()
{
InitializeComponent();
client.DoWorkCompleted += new EventHandler<DoWorkCompletedEventArgs>(client_DoWorkCompleted);
}
void client_DoWorkCompleted(object sender, DoWorkCompletedEventArgs e)
{
Cache.Instance[KEY_CACHE] = e.Result;
Message("Service", "List's count:"+e.Result.Count().ToString());
}
private void btnTest_Click(object sender, RoutedEventArgs e)
{
if (Cache.Instance[KEY_CACHE] == null)
{
client.DoWorkAsync();
}
else
{
Message("Cache", "List's count:" + (Cache.Instance[KEY_CACHE] as ObservableCollection<Something>).Count().ToString());
}
}
void Message(string from, string msg)
{
MessageBox.Show(string.Format("Data from {0}, msg is {1}", from, msg));
}
}
有兴趣的下个demo看看/Files/Hedonister/SilverlightCache.zip
ps:好久不写东西,不会说话了,一片帖子发了一个多小时