这里所说的个性化、灵活、实时更新的定义?
个性化,是指你可以随意定义自己想要的配置结构、保存格式、存放位置等等。
灵活,是指可以方便的对配置进行读、写操作,并可以很容易实现任意多个配置管理器。
实时更新,是指在配置发生改变时可以实时的更新,且不会重启Web应用程序。
下面开始讲解设计。既然是配置管理器,那还是先定义好接口吧,请看IFileConfigManager<T>:
/// <summary>
/// Interface containing all properties and methods to be implemented
/// by file configuration manager.
/// </summary>
/// <typeparam name="T">The type of config entity.</typeparam>
public
interface
IFileConfigManager<T> : IDisposable
where T :
class
,
new
()
{
/// <summary>
/// Gets the path of the config file.
/// </summary>
string
Path {
get
; }
/// <summary>
/// Gets the encoding to read or write the config file.
/// </summary>
Encoding Encoding {
get
; }
/// <summary>
/// Gets the serializer of the config manager for loading or saving the config file.
/// </summary>
FileConfigSerializer<T> Serializer {
get
; }
/// <summary>
/// Gets the current config entity.
/// </summary>
/// <returns></returns>
T GetConfig();
/// <summary>
/// Saves the current config entity to file.
/// </summary>
void
SaveConfig();
/// <summary>
/// Saves a specified config entity to file.
/// </summary>
/// <param name="config"></param>
void
SaveConfig(T config);
/// <summary>
/// Backups the current config entity to a specified path.
/// </summary>
/// <param name="backupPath"></param>
void
BackupConfig(
string
backupPath);
/// <summary>
/// Restores config entity from a specified path and saves to the current path.
/// </summary>
/// <param name="restorePath"></param>
void
RestoreConfig(
string
restorePath);
}
|
T参数当然就是定义的配置类型了,而且必须是引用类型,有无参数构造函数。Path是配置文件的完整路径,Encoding是读取和保存配置时用的编码,Serializer是处理配置序列化和反序列化的具体实现,GetConfig()是获取当前配置,SaveConfig()是保存当前配置,SaveConfig(T config)是保存指定的配置,BackupConfig(string backupPath)备份配置到指定路径,RestoreConfig(string restorePath)从指定路径还原配置。
接口IFileConfigManager<T>中定义的Serializer是用于支持自定义配置序列化功能的,下面看看FileConfigSerializer<T>的实现:
public
abstract
class
FileConfigSerializer<T>
where T :
class
,
new
()
{
#region Fields
// XML格式
public
static
readonly
FileConfigSerializer<T> Xml =
new
XmlFileConfigSerializer();
// 二进制格式
public
static
readonly
FileConfigSerializer<T> Binary =
new
BinaryFileConfigSerializer();
#endregion
#region Methods
// 从配置文件反序列化,使用指定的编码
public
abstract
T DeserializeFromFile(
string
path, Encoding encoding);
// 序列化到配置文件,使用指定的编码
public
abstract
void
SerializeToFile(T config,
string
path, Encoding encoding);
#endregion
#region XmlFileConfigSerializer
// 实现默认的Xml序列化类
private
sealed
class
XmlFileConfigSerializer : FileConfigSerializer<T>
{
public
override
T DeserializeFromFile(
string
path, Encoding encoding)
{
return
SerializationUtil.DeserializeFromXmlFile<T>(path, encoding);
}
public
override
void
SerializeToFile(T config,
string
path, Encoding encoding)
{
SerializationUtil.SerializeToXmlFile(config, path, encoding);
}
}
#endregion
#region BinaryFileConfigSerializer
// 实现默认的二进制序列化类
private
sealed
class
BinaryFileConfigSerializer : FileConfigSerializer<T>
{
public
override
T DeserializeFromFile(
string
path, Encoding encoding)
{
return
SerializationUtil.DeserializeFromBinaryFile<T>(path, encoding);
}
public
override
void
SerializeToFile(T config,
string
path, Encoding encoding)
{
SerializationUtil.SerializeToBinaryFile(config, path, encoding);
}
}
#endregion
}
|
FileConfigSerializer<T>定义为抽象类,是为了方便默认的使用和扩展,里面使用的SerializationUtil类,是本人为了方便写的一个简单的序列化助手类,相信大家对对象的序列化操作不会陌生了,无非使用了System.Xml.Serialization.XmlSerializer、System.Runtime.Serialization.Formatters.Binary.BinaryFormatter、System.Runtime.Serialization.Json.DataContractJsonSerializer和System.Runtime.Serialization.NetDataContractSerializer来处理。如果不想用它们,你还可以实现FileConfigSerializer<T>进行完全的自己定义配置的加载与保存方式。对于json序列化推荐大家使用http://www.codeplex.com/json/ 。
好了,大家已经知道了接口的定义了,下面来讲讲实时更新配置功能有哪些方法可以实现。我们知道,如果利用Web.config来配置的话,第一:如果配置内容多而杂,那会很乱;第二:如果手动修改配置,会导致Web重启(而我们并不希望它重启),所以,如果要解决上面两点问题,我们就要思考点什么了。上面我提到了Discuz!论坛的.net开源版本里配置管理,它是使用Timer来定时查检配置是否有修改,如果有修改就重新加载的,恩,这是一个可行的方案。还有其它方法吗?必须是有的,只要你肯去思考,下面列出本人想到的几个比较容易想到的方案:
方法1:使用Timer(.net库里有三个timer,请自行选择),每隔一秒就查检一下配置文件修改时间,如果文件被修改了,正更新最后修改时间并重新加载配置内容;
方法2:使用System.IO.FileSystemWatcher,可以实时监控配置文件,一发生改变即重新加载配置内容;
方法3:使用System.Web.Caching.Cache,加上缓存依赖,文件更改后缓存会失效,同样可以实时重新加载配置内容。
这三种方法中,方法3是本人比较推荐的,因为它的开销最小,而且可以实时更新配置,实现起来也是最简单的。对于新手可能看到这还不知道实现,下面再贴出本人实现上面接口的四个类,一个是默认管理器类,没有实时更新的功能,其它三个就是实现上面三种方法的管理器类了。
internal
class
DefaultFileConfigManager<T> : DisposableObject, IFileConfigManager<T>
where T :
class
,
new
()
{
#region Fields
private
string
path =
null
;
private
Func<
string
> pathCreator =
null
;
#endregion
#region Constructors
public
DefaultFileConfigManager(Func<
string
> pathCreator, FileConfigSerializer<T> serializer, Encoding encoding)
{
pathCreator.ThrowsIfNull(
"pathCreator"
);
serializer.ThrowsIfNull(
"serializer"
);
this
.pathCreator = pathCreator;
this
.Encoding = encoding;
this
.Serializer = serializer;
this
.SyncRoot =
new
object
();
this
.Config =
null
;
}
#endregion
#region Properties
public
string
Path
{
get
{
if
(
this
.path ==
null
)
{
string
path =
this
.pathCreator();
path.ThrowsIfNull(
"The path returned form pathCreator is null."
);
this
.path = path;
this
.LazyInitialize();
}
return
this
.path;
}
}
public
Encoding Encoding
{
get
;
protected
set
;
}
public
FileConfigSerializer<T> Serializer
{
get
;
protected
set
;
}
protected
object
SyncRoot
{
get
;
set
;
}
protected
virtual
T Config
{
get
;
set
;
}
#endregion
#region Methods
public
virtual
T GetConfig()
{
if
(
this
.Config ==
null
)
{
lock
(
this
.SyncRoot)
{
if
(
this
.Config ==
null
)
{
FileInfo file =
new
FileInfo(
this
.Path);
if
(!file.Exists)
{
// make sure the existence of the config directory
if
(!file.Directory.Exists)
{
file.Directory.Create();
}
// save the default config to file
this
.Config =
new
T();
this
.Serializer.SerializeToFile(
this
.Config,
this
.Path,
this
.Encoding);
}
else
{
// else, loads from the specified path
this
.Config =
this
.Serializer.DeserializeFromFile(
this
.Path,
this
.Encoding);
}
}
}
}
return
this
.Config;
}
public
void
SaveConfig()
{
this
.SaveConfig(
this
.GetConfig());
}
public
virtual
void
SaveConfig(T config)
{
config.ThrowsIfNull(
"config"
);
lock
(
this
.SyncRoot)
{
FileInfo file =
new
FileInfo(
this
.Path);
// make sure the existence of the config directory
if
(!file.Directory.Exists)
{
file.Directory.Create();
}
this
.Config = config;
this
.Serializer.SerializeToFile(
this
.Config,
this
.Path,
this
.Encoding);
}
}
public
void
BackupConfig(
string
backupPath)
{
backupPath.ThrowsIfNull(
"backupPath"
);
T config =
this
.GetConfig();
this
.Serializer.SerializeToFile(config, backupPath,
this
.Encoding);
}
public
void
RestoreConfig(
string
restorePath)
{
restorePath.ThrowsIfNull(
"restorePath"
);
T config =
this
.Serializer.DeserializeFromFile(restorePath,
this
.Encoding);
this
.SaveConfig(config);
}
// this method is provided to subclasses to initialize their data
protected
virtual
void
LazyInitialize()
{
}
#endregion
}
|
internal
sealed
class
FileConfigManagerWithTimer<T> : DefaultFileConfigManager<T>
where T :
class
,
new
()
{
private
Timer timer =
null
;
private
DateTime lastWriteTime = DateTime.MinValue;
// a flag to notify us of the change config
public
FileConfigManagerWithTimer(Func<
string
> pathCreator, FileConfigSerializer<T> serializer, Encoding encoding)
:
base
(pathCreator, serializer, encoding)
{
}
protected
override
void
LazyInitialize()
{
base
.LazyInitialize();
// initializes the timer, with it's interval of 1000 milliseconds
this
.timer =
new
Timer(1000);
this
.timer.Enabled =
true
;
this
.timer.AutoReset =
true
;
this
.timer.Elapsed +=
new
ElapsedEventHandler(Timer_Elapsed);
this
.timer.Start();
}
protected
override
void
Dispose(
bool
disposing)
{
if
(disposing)
{
// disposes the timer
this
.timer.Dispose();
this
.timer =
null
;
}
}
private
void
Timer_Elapsed(
object
sender, ElapsedEventArgs e)
{
if
(!File.Exists(
this
.Path))
{
// the file has been deleted
return
;
}
var tempWriteTime = File.GetLastWriteTime(
this
.Path);
// if equals to the initial value, update it and return
if
(
this
.lastWriteTime == DateTime.MinValue)
{
this
.lastWriteTime = tempWriteTime;
return
;
}
// if no equals to new write time, update it and reload config
if
(
this
.lastWriteTime != tempWriteTime)
{
this
.lastWriteTime = tempWriteTime;
lock
(
this
.SyncRoot)
{
this
.Config =
this
.Serializer.DeserializeFromFile(
this
.Path,
this
.Encoding);
}
}
}
}
|
internal
sealed
class
FileConfigManagerWithFileWatcher<T> : DefaultFileConfigManager<T>
where T :
class
,
new
()
{
private
FileWatcher watcher =
null
;
public
FileConfigManagerWithFileWatcher(Func<
string
> pathCreator, FileConfigSerializer<T> serializer, Encoding encoding)
:
base
(pathCreator, serializer, encoding)
{
}
protected
override
void
LazyInitialize()
{
base
.LazyInitialize();
// when the path is created, the watcher should be initialize at the same time
watcher =
new
FileWatcher(
this
.Path, FileChanged);
// just start watching the file
watcher.StartWatching();
}
protected
override
void
Dispose(
bool
disposing)
{
if
(disposing)
{
// disposes the watcher
this
.watcher.Dispose();
this
.watcher =
null
;
}
base
.Dispose(disposing);
}
private
void
FileChanged(
object
sender, FileSystemEventArgs args)
{
lock
(
this
.SyncRoot)
{
this
.watcher.StopWatching();
try
{
// note: here making the cuurent thread sleeping a litle while to avoid exception throwed by watcher
Thread.Sleep(10);
// reload the config from file
this
.Config =
this
.Serializer.DeserializeFromFile(
this
.Path,
this
.Encoding);
}
catch
(Exception)
{
// ignore it
}
finally
{
this
.watcher.StartWatching();
}
}
}
}
|
internal
sealed
class
FileConfigManagerWithCacheDependency<T> : DefaultFileConfigManager<T>
where T :
class
,
new
()
{
const
string
KeyPrefix =
"FileConfig:"
;
public
FileConfigManagerWithCacheDependency(Func<
string
> pathCreator, FileConfigSerializer<T> serializer, Encoding encoding)
:
base
(pathCreator, serializer, encoding)
{
}
protected
override
T Config
{
get
{
return
HttpRuntime.Cache[KeyPrefix +
this
.Path]
as
T;
}
set
{
// if not null, update the cache value
if
(value !=
null
)
{
HttpRuntime.Cache.Insert(KeyPrefix +
this
.Path, value,
new
CacheDependency(
this
.Path), DateTime.
|