Weicco.Lib.Configuration

weicco 06.06.09 17:43

Konfigurointiluokka joka toimii singleton periaatteella.

 Tekstiversio  Arvo: 1 (3 ääntä)  Äänestä: +  -
/*
    Copyright (c) 2009 Marko Parkkola

    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.
 */


using System;
using System.IO;
using System.Reflection;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace Weicco.Lib.Configuration
{
    public sealed class ConfigurationManager<TConf> where TConf : ConfigurationBase
    {
        private static readonly ConfigurationBase _config = null;

        static ConfigurationManager()
        {
            _config = Activator.CreateInstance<TConf>();
        }

        public ConfigurationManager()
        {
        }

        /// <summary>
        /// Returns the actual configuration object.
        /// </summary>
        /// <returns>Configuration object.</returns>
        public TConf GetConfiguration()
        {
            return (TConf)_config;
        }

        private string ConfigurationPathBase
        {
            get
            {
                string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) +
                    "\\Weicco.Lib.Configuration\\";

                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }

                return path;
            }
        }

        /// <summary>
        /// Full path where configuration file is found.
        /// </summary>
        public string ConfigurationPath
        {
            get
            {
                return ConfigurationPathBase + _config.Name + ".xml";
            }
        }

        /// <summary>
        /// Loads and deserializes configuration object into memory.
        /// </summary>
        public void Load()
        {
            Type type = typeof(TConf);
            var query = from pi in type.GetProperties().OfType<System.Reflection.PropertyInfo>()
                        where pi.CanRead && pi.CanWrite && !pi.PropertyType.IsArray && !pi.PropertyType.IsInterface
                        select pi;

            XDocument config = XDocument.Load(ConfigurationPath);
            foreach (var pi in query)
            {
                XElement element = (from e in config.Descendants()
                                    where e.Attribute("Name") != null && e.Attribute("Name").Value == pi.Name
                                    select e).FirstOrDefault();

                XmlSerializer ser = new XmlSerializer(pi.PropertyType);
                pi.SetValue(_config, ser.Deserialize(element.CreateReader()), null);
            }

            _config.Loading(config.Descendants("values").Single());
        }

        /// <summary>
        /// Serializes and saves configuration object to file.
        /// </summary>
        public void Save()
        {
            Type type = typeof(TConf);
            var query = from pi in type.GetProperties().OfType<System.Reflection.PropertyInfo>()
                        where pi.CanRead && pi.CanWrite && !pi.PropertyType.IsArray && !pi.PropertyType.IsInterface
                        select pi;

            XDocument config = new XDocument(new XElement(XName.Get("configuration"), new XElement("values")));

            if (query.Count() > 0)
            {
                foreach (var pi in query)
                {
                    using (MemoryStream ms = new MemoryStream())
                    {
                        XmlSerializer ser = new XmlSerializer(pi.PropertyType);
                        ser.Serialize(ms, pi.GetValue(_config, null));

                        ms.Position = 0;
                       
                        XElement e = XElement.Load(ms);
                        e.Add(new XAttribute("Name", pi.Name));

                        config.Descendants("values").Single().Add(e);
                    }
                }
            }

            _config.Saving(config.Descendants("values").Single());

            config.Save(ConfigurationPath);
        }

        /// <summary>
        /// Checks if configuration file exists.
        /// </summary>
        /// <returns>True if file exists, false otherwise.</returns>
        /// <remarks>Validity of configuration file is not checked.</remarks>
        public bool Exists()
        {
            return File.Exists(ConfigurationPath);
        }

        private FileSystemWatcher fileWatcher = null;

        /// <summary>
        /// Waits for change in configuration file.
        /// </summary>
        /// <remarks>Waiting is done synchronously so this method blocks until configuration file is changed.</remarks>
        public void WaitForChange()
        {
            if (fileWatcher == null)
            {
                fileWatcher = new FileSystemWatcher(ConfigurationPathBase);
                fileWatcher.Filter = Path.GetFileName(ConfigurationPath);
            }

            fileWatcher.WaitForChanged(WatcherChangeTypes.All);
        }
    }

    public abstract class ConfigurationBase
    {
        protected ConfigurationBase(string name)
        {
            Name = name;
        }

        /// <summary>
        /// Name of the configuration (and configuration file).
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Override this for custom deserialization.
        /// </summary>
        /// <param name="baseElement">Base element under which all value elements are found.</param>
        protected internal virtual void Loading(XElement baseElement)
        {
        }

        /// <summary>
        /// Override this for custom serialization.
        /// </summary>
        /// /// <param name="baseElement">Base element under which you can save your own elements.
        /// Take notice not to don't add any elements with Name attribute which already exists in configuration XML.</param>
        protected internal virtual void Saving(XElement baseElement)
        {
        }
    }
}

weicco 17:45 6.6.09 
No niin eli käyttöohjeet. Luo oma konfiguraatioluokka esimerkiksi tällainen typeryys:


C#
class UiConfiguration : ConfigurationBase
    {
        public UiConfiguration()
            : base("ConfigurationTest.UiConfiguration")
        {
        }

        public Point Location { get; set; }
        public Size Size { get; set; }
        public string Text { get; set; }
    }
 


Sen jälkeen voit käskyttää luokkaa tähän malliin (Windows Forms sovellus, Form_Loadista):


C#
ConfigurationManager<UiConfiguration> config = new ConfigurationManager<UiConfiguration>();
   
    config.Load();

    UiConfiguration uiConfig = config.GetConfiguration();
    Location = uiConfig.Location;
    Size = uiConfig.Size;
    Text = uiConfig.Text;
 


Ja tähän malliin (Form_Closing):


C#
ConfigurationManager<UiConfiguration> config = new ConfigurationManager<UiConfiguration>();
    UiConfiguration uiConfig = config.GetConfiguration();

    uiConfig.Location = Location;
    uiConfig.Size = Size;
    uiConfig.Text = Text;

    config.Save();
 
weicco 17:47 6.6.09 
Huomauttaisin vielä, että tuo on vähän keskeneräinen, joskin toimiva. Minulla on vielä pari ideaa siihen kuten onnistuisikohan implisiittinen castaus ConfigurationManagerista TConf tyyppiin...
weicco 17:33 8.6.09 
Tässä vielä samanmoinen C# 4.0 versiolla tehtynä. Toimii VS2010 betalla.


C#
/*
    Copyright (c) 2009 Marko Parkkola

    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.
 */


using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace Weicco.Lib.Configuration.Dynamic
{
    public class Configuration : DynamicObject
    {
        public Configuration(string name)
        {
            Name = name;
            if (!_configs.ContainsKey(Name))
            {
                _configs.Add(Name, new Dictionary<string, object>());
            }
        }

        private static Dictionary<string, Dictionary<string, object>> _configs = new Dictionary<string, Dictionary<string, object>>();

        private string ConfigurationPathBase
        {
            get
            {
                string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) +
                    "\\Weicco.Lib.Configuration\\";

                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }

                return path;
            }
        }

        public string Name { get; private set; }

        /// <summary>
        /// Full path where configuration file is found.
        /// </summary>
        public string ConfigurationPath
        {
            get
            {
                return ConfigurationPathBase + Name + ".xml";
            }
        }

        public bool Exists
        {
            get
            {
                return File.Exists(ConfigurationPath);
            }
        }

        public void Load()
        {
            XDocument config = XDocument.Load(ConfigurationPath);
            foreach (var element in from e in config.Descendants()
                                    where e.Attribute("PropertyName") != null
                                    select e)
            {
                XmlSerializer ser = new XmlSerializer(Type.GetType(element.Attribute("FullName").Value));
                object value = ser.Deserialize(element.CreateReader());

                _configs[Name][element.Attribute("PropertyName").Value] = value;
            }
        }

        public void Save()
        {
            XDocument config = new XDocument(
                new XElement("Configuration",
                    new XElement("Values",
                        from kvp in _configs[Name]
                        select Serialize(kvp)
                        )
                    )
                );

            config.Save(ConfigurationPath);
        }

        private XElement Serialize(KeyValuePair<string, object> kvp)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                XmlSerializer ser = new XmlSerializer(kvp.Value.GetType());
                ser.Serialize(ms, kvp.Value);

                ms.Position = 0;
               
                XElement element = XElement.Load(ms);
                element.Add(new XAttribute("FullName", kvp.Value.GetType().AssemblyQualifiedName));
                element.Add(new XAttribute("PropertyName", kvp.Key));

                return element;
            }
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var config = _configs[Name];
            result = config.ContainsKey(binder.Name) ? config[binder.Name] : null;
            return config.ContainsKey(binder.Name);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            var config = _configs[Name];
            config[binder.Name] = value;
            return true;
        }
    }
}
 


Ja käyttö:


C#
dynamic conf = new Weicco.Lib.Configuration.Dynamic.Configuration("ConfigurationTest");
conf.Foo = 1;
conf.Bar = "baz";
conf.Save();

// jne.
 
weicco 17:38 8.6.09 
Jaahmm... Color tyyppi ei oikein tykkää serialisoitua. Outoa. Ehkä joku .NET 4.0 betan ongelmia.
PaulMuadDib 15:37 12.6.09 
Ratkaisusi käyttää abstraktia luokkaa esittelemättä ollenkaan rajapintaa IConfiguration tjms. maistuu minun suuhuni ehkä hieman oudolta. Rajapintaa käyttämällä päästäisiin ehkä hieman parempaan abstraktioon.
weicco 22:24 13.6.09 
Olet oikeassa. Minulla oli vain joku idea mitä tuohon abstraktiin luokkaan tulisi, sellaista joka vaatisi että se olisi abstrakti eikä rajapinta, mutta kun en enää muista koko touhua. Innostuin niin noista dynaamisista jutuista (koodasin pienen ORM:n), että unohdin koko konfiguraatioluokan :)