How to design a personalized, flexible, and real-time Update Configuration Manager? Thoughts on implementation

Source: Internet
Author: User
Tags object serialization

I haven't written any articles for some days. I will share with you my thoughts on Configuration Management tonight. This implementation is mainly suitable for small and medium-sized applications (web or winform). If your website requires Server Load balancer, this solution is not applicable. We recommend that you save the configuration in the database or distributed cache, if you have a better idea, give me some advice. This configuration was completed during the development of the SNS website in. At that time, I saw discuz! . Net open-source version, think that its configuration management is not flexible enough to think of using generics to achieve their own configuration management components. Today we will talk about two more functions than the first version of 09: delayed loading of configuration paths and custom configuration serialization. The late loading of paths is the result of reading Uncle Tom's Configuration Management Article. In fact, this small function can be easily implemented without lazy of. net4.0, because the demand is too simple.

 

What is the definition of personalization, flexibility, and real-time update?

PersonalizationYou can define the configuration structure, storage format, and storage location as needed.

FlexibleIt is convenient to read and write configurations, and can easily implement any number of configuration managers.

Real-time updateIt means that the configuration can be updated in real time without restarting the web application.

 

Ifileconfigmanager <t>

The following describes the design. Since it is the Configuration Manager, define the interface first. See 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);    }

 

The t parameter is of course the defined configuration type, and must be a reference type, with or without a parameter constructor. PATH is the complete path of the configuration file, encoding is the encoding used to read and save the configuration, serializer is the specific implementation for processing configuration serialization and deserialization, getconfig () is to get the current configuration, saveconfig () is to save the current configuration, saveconfig (T config) is to save the specified configuration, backupconfig (string backuppath) backup configuration to the specified path, restoreconfig (string restorepath) restore the configuration from the specified path.

 

Fileconfigserializer <t>

The serializer defined in the ifileconfigmanager interface is used to support custom configuration serialization. Let's take a look at it.Fileconfigserializer <t>Implementation:

Public abstract class fileconfigserializer <t> where T: Class, new () {# region fields // XML format public static readonly fileconfigserializer <t> xml = new xmlfileconfigserializer (); // binary format public static readonly fileconfigserializer <t> binary = new binaryfileconfigserializer (); # endregion # Region Methods // deserialize from the configuration file, use the specified public abstract t deserializefromfile (string path, encoding); // serialize to the configuration file, use the specified public abstract void serializetofile (T config, string path, encoding encoding); # endregion # region xmlfileconfigserializer // implement the default XML serialization class private sealed class xmlfileconfigserializer: fileconfigserializer <t> {public override t encode (string path, encoding) {return serializationutil. deserializefromxmlfile <t> (path, encoding);} public override void serializetofile (T config, string path, encoding) {serializationutil. serializetoxmlfile (config, path, encoding) ;}# endregion # region binaryfileconfigserializer // implement the default binary serialization class private sealed class binaryfileconfigserializer: fileconfigserializer <t> {public override t deserializefromfile (string path, encoding) {return serializationutil. deserializefrombinaryfile <t> (path, encoding);} public override void serializetofile (T config, string path, encoding) {serializationutil. serializetobinaryfile (config, path, encoding) ;}# endregion}

Fileconfigserializer <t> is defined as an abstract class to facilitate the use and expansion by default. The serializationutil class is used in it. It is a simple serialization Assistant class for the convenience of writing, I believe that you will not be unfamiliar with Object serialization operations, instead of using system. XML. serialization. xmlserializer, system. runtime. serialization. formatters. binary. binaryformatter, system. runtime. serialization. JSON. datacontractjsonserializer and system. runtime. serialization. netdatacontractserializer. If you do not want to use them, you can also implement fileconfigserializer <t> to completely customize the configuration loading and saving methods. Http://www.codeplex.com/json/ is recommended for JSON serialization.

 

The four functions used for serialization are implemented as follows:
        public static void SerializeToXmlFile(object obj, string path, Encoding encoding)        {            using (var sw = new StreamWriter(path, false, encoding))            {                new XmlSerializer(obj.GetType()).Serialize(sw, obj);            }        }        public static object DeserializeFromXmlFile(string path, Type type, Encoding encoding)        {            object obj = null;            using (var sr = new StreamReader(path, encoding))            {                using (var xtr = new XmlTextReader(sr))                {                    xtr.Normalization = false;                    obj = new XmlSerializer(type).Deserialize(xtr);                }            }            return obj;        }        public static void SerializeToBinaryFile(object obj, string path, Encoding encoding)        {            byte[] bytes = null;            using (var ms = new MemoryStream())            {                new BinaryFormatter().Serialize(ms, obj);                ms.Position = 0;                bytes = new Byte[ms.Length];                ms.Read(bytes, 0, bytes.Length);                using (var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))                {                    using (var bw = new BinaryWriter(fs, encoding))                    {                        bw.Write(bytes);                    }                }            }        }        public static object DeserializeFromBinaryFile(string path, Encoding encoding)        {            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))            {                using (var br = new BinaryReader(fs, encoding))                {                    byte[] bytes = new byte[fs.Length];                    br.Read(bytes, 0, (int)fs.Length);                    using (var ms = new MemoryStream())                    {                        ms.Write(bytes, 0, bytes.Length);                        ms.Position = 0;                        return new BinaryFormatter().Deserialize(ms);                    }                }            }        }

 

Real-time update

Now that you know the interface definition, let's talk about how to update the configuration function in real time. We know that if you use web. first, if the configuration content is too complex, it will be messy; second, if you manually modify the configuration, it will cause web restart (and we do not want it to restart, if we want to solve the above two problems, we have to think about something. I mentioned discuz above! Configuration Management in the. NET open-source version of the Forum. It uses timer to regularly check whether the configuration has been modified. If there is any modification, it will be reloaded. Well, this is a feasible solution. Are there other methods? There must be something. As long as you are willing to think about it, I will list several easy-to-think solutions below:

Method 1: Use timer (. net Library has three timer, please select), check the configuration file modification time every second, if the file is modified, is updating the last modification time and re-load the configuration content;

Method 2: using system. Io. filesystemwatcher, You can monitor the configuration file in real time and reload the configuration content as soon as it changes;

Method 3: When system. Web. caching. cache is used and the cache dependency is added, the cache becomes invalid after the file is changed, and the configuration content can be reloaded in real time.

Among the three methods, method 3 is recommended by myself because it has the minimum overhead and can update configurations in real time, which is also the easiest to implement. If you are a newbie, you may not know the implementation yet. Next I will post four classes that implement the above interfaces. One is the default manager class and there is no real-time update function, the other three are the manager classes that implement the above three methods.

    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.Now.AddYears(1), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);                }            }        }    }

 

It is worth noting that the func <string> pathcreator parameter of the default manager defaultfileconfigmanager <t> is used to implement delayed loading of the configuration file, you do not need to create a static constructor or global. I initialized the manager instance in asax. In addition, I wrote another class to return the created manager instance for ease of use. This is nothing to say, that is why the access scope of the above classes is within the Assembly.At this point, the entire implementation idea and most code implementations have been completed, and I hope to help you :) for more attention:Kudystudio document directory

 

If you are interested, you can download this example to see the effect of various methods: fileconfigweb.rar

 

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.