Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

A Flexible Plugin System

, 3 Sep 2008
A generic plugin system used to load and manage plugins
fadd-15373.zip
trunk
dlls
xunit.dll
Examples
Plugins
ExampleApplication.Shared
Properties
ExampleApplication
Properties
ExamplePlugin.Shared
Properties
ExamplePlugin
Properties
Fadd.Globalization.Yaml
fadd.snk
Properties
Tests
fadd
Commands
Net
Tests
fadd.snk
Globalization
Tests
Logging
Plugins
Properties
Tests
Validation
using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.CSharp;
using Xunit;

namespace Fadd.Commands.Net
{
    /// <summary>
    /// Copies get/set properties from one object to another.
    /// </summary>
    internal class ObjectCopier : IDisposable
    {
        private const string BindingTemplate =
            @"{NameSpaces}

namespace Fadd.Commands.Net
{
    public class {TypeName}Binding : Binding
    {
        private static Type MyType = typeof({TypeName});

        public void Copy(object fromValue, object toValue)
        {
            {FullTypeName} from = ({FullTypeName})fromValue;
            {FullTypeName} to = ({FullTypeName})toValue;

{CopyProperties}
        }
    }
}
";

        private Dictionary<Type, Binding> _bindings = new Dictionary<Type, Binding>();

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="ObjectCopier"/> is reclaimed by garbage collection.
        /// </summary>
        ~ObjectCopier()
        {
            Dispose();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            if (_bindings == null) return;
            _bindings.Clear();
            _bindings = null;
        }

        /// <summary>
        /// Copies all get/set properties from one object to another.
        /// </summary>
        /// <param name="from">From.</param>
        /// <param name="to">To.</param>
        public void Copy(object from, object to)
        {
            Check.Require(from, "from");
            Check.Require(to, "to");
            Type type = to.GetType();
            if (!type.IsAssignableFrom(from.GetType()))
                throw new ArgumentException(type.FullName + " must be assignable from " + from.GetType().FullName);

            lock (_bindings)
            {
                if (_bindings.ContainsKey(type))
                {
                    _bindings[type].Copy(from, to);
                    return;
                }
            }

            Binding binding = GenerateBinding(type);
            lock (_bindings)
            {
                if (!_bindings.ContainsKey(type))
                    _bindings.Add(type, binding);
            }

            binding.Copy(from, to);
        }

        private static Binding GenerateBinding(Type type)
        {
            string code = GenerateCode(type);
            return Compile(type, code);
        }

        private static string GenerateCode(Type type)
        {
            string properties = string.Empty;
            Type enumerableType = typeof (IEnumerable);

            PropertyInfo[] pis = type.GetProperties();
            foreach (PropertyInfo pi in pis)
            {
                if (pi.CanRead && pi.CanWrite)
                    properties += "\t\tto." + pi.Name + " = from." + pi.Name + ";\r\n";
                if (pi.CanWrite)
                    continue;

                // handle lists.
                Type itemType;
                if (pi.PropertyType.IsGenericType)
                {
                    Type[] types = pi.PropertyType.GetGenericArguments();
                    if (types.Length != 1)
                        throw new NotSupportedException("Tunnel does not support generic lists with multiple types.");
                    itemType = types[0];
                }
                else
                    itemType = typeof(object);

                MethodInfo addMethod = pi.PropertyType.GetMethod("Add", new Type[] {itemType});
                if (addMethod != null && enumerableType.IsAssignableFrom(pi.PropertyType))
                {
                    properties += "\t\tforeach (" + itemType + " value in from." + pi.Name + ")" + Environment.NewLine +
                                  "\t\t\tto." + pi.Name + ".Add(value);" + Environment.NewLine;
                }
            }

            string bindingCode = BindingTemplate;
            bindingCode = bindingCode.Replace("{NameSpaces}", "using " + type.Namespace + ";");
            bindingCode = bindingCode.Replace("{TypeName}", type.Name);
            bindingCode = bindingCode.Replace("{FullTypeName}", type.FullName.Replace("+", "."));
            bindingCode = bindingCode.Replace("{CopyProperties}", properties);
            return bindingCode;
        }

        [Fact]
        private static void TestGenerateCode()
        {
            string code = GenerateCode(typeof (TestClass));
            const string expectedCode =
                @"using Fadd.Commands.Net;

namespace Fadd.Commands.Net
{
    public class TestClassBinding : Binding
    {
        public void Copy(object fromValue, object toValue)
        {
            Fadd.Commands.Net.ObjectCopier.TestClass from = (Fadd.Commands.Net.ObjectCopier.TestClass)fromValue;
            Fadd.Commands.Net.ObjectCopier.TestClass to = (Fadd.Commands.Net.ObjectCopier.TestClass)toValue;

		foreach (System.String value in from.Items)
			to.Items.Add(value);
		to.UserName = from.UserName;

        }
    }
}
";
            Assert.Equal(expectedCode, code);
        }

#if DEBUG
        #region TestClass
        // ReSharper disable UnusedMemberInPrivateClass
        /// <summary>
        /// </summary>
        public class TestClass
        {
            private readonly List<string> _items = new List<string>();
            private string _userName;

            /// <summary>
            /// Gets the items.
            /// </summary>
            /// <value>The items.</value>
            public List<string> Items
            {
                get { return _items; }
            }

            /// <summary>
            /// Gets or sets the name of the user.
            /// </summary>
            /// <value>The name of the user.</value>
            public string UserName

            {
                get { return _userName; }
                set { _userName = value; }
            }
        }
        // ReSharper restore UnusedMemberInPrivateClass
        #endregion
#endif
        private static Binding Compile(Type type, string code)
        {
            Check.Require(type, "type");
            Check.NotEmpty(code, "code");

            

            CSharpCodeProvider codeProvider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters();
            parameters.GenerateExecutable = false;
            parameters.GenerateInMemory = true;
            parameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
            parameters.ReferencedAssemblies.Add(type.Assembly.Location);

            CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code);
            if (results.Errors.Count > 0)
            {
                string errs = "";

                foreach (CompilerError CompErr in results.Errors)
                    errs += "Line #" + CompErr.Line + ", " + CompErr.ErrorNumber + " '" + CompErr.ErrorText + "'" +
                            Environment.NewLine;
                InvalidOperationException err = new InvalidOperationException(errs);
                err.Data.Add("code", code);
                throw err;
            }

            string typeName = "Fadd.Commands.Net." + type.Name + "Binding";
            Assembly generatorAssembly = results.CompiledAssembly;
            return
                (Binding)
                generatorAssembly.CreateInstance(typeName, false, BindingFlags.CreateInstance,
                                                 null, null, null, null);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

jgauffin
Founder Gauffin Interactive AB
Sweden Sweden
Founder of OneTrueError, a .NET service which captures, analyzes and provide possible solutions for exceptions.
 
blog | twitter
Follow on   Twitter   LinkedIn

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 3 Sep 2008
Article Copyright 2008 by jgauffin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid