Mapping a dynamic object to an interface and register with IoC

2.9k views Asked by At

I'm trying to register dynamic implementations for interfaces that will be injected into objects created by my IoC container (Unity in this case).

Here is the high-level approach I'm taking:

  1. Dynamically load a list of properties from a JSON file. I'm using JSON.NET for this currently.
  2. Map that dynamic object to an interface. I'm currently using Impromptu for this.
  3. Register that dynamic object with my IoC container for the interface type

Here is the code that should "theoretically" work:

var configJson = File.ReadAllText(".\\Configuration\\DataCollector.json");
dynamic expando = JsonConvert.DeserializeObject(configJson);
var container = new UnityContainer();
var interfaceType = Type.GetType("Manufacturing.Framework.Configuration.IDataCollectorConfiguration", true);
var interfaceInstance = Impromptu.ActLike(expando, interfaceType);

container.RegisterInstance(interfaceType, "IDataCollectorConfiguration", interfaceInstance, new ContainerControlledLifetimeManager());

All is well until the last line. Unity doesn't like the fact that I'm not giving it an actual interface instance, just a duck typed instance.

The type ImpromptuInterface.ActLikeCaster cannot be assigned to variables of type Manufacturing.Framework.Configuration.IDataCollectorConfiguration

Why am I doing this? I'm trying to simplify my complex application configuration by storing my settings as JSON, defining interfaces to map to that JSON, and then have my IoC container automatically inject the proper configuration into any class that asks for it.

2

There are 2 answers

2
Brandon Martinez On BEST ANSWER

If you didn't need to use interfaces, you could get by using concrete types:

using System;
using Microsoft.Practices.Unity;
using Newtonsoft.Json;

namespace TestGrounds
{
    public class TestClass
    {
        #region Properties

        public int TestIntegerProperty { get; set; }

        public string TestStringProperty { get; set; }

        #endregion
    }

    internal class Program
    {
        #region Static Methods

        private static void Main(string[] args)
        {
            const string json =
                @"{ TestIntegerProperty: 1, TestStringProperty: 'Hello', AnotherTestPropertyToIgnore: 'Sup' }";

            registerDependencyFromJson<TestClass>(json);

            Console.ReadKey();
        }

        private static void registerDependencyFromJson<T>(string json) where T: class, new()
        {
            var deserializedObject = JsonConvert.DeserializeObject<T>(json);
            var type = deserializedObject.GetType();
            var container = new UnityContainer();

            container.RegisterInstance(type, type.Name, deserializedObject, new ContainerControlledLifetimeManager());
        }

        #endregion
    }
}

Which might be better anyway, since interfaces could have required method implementations on them that any sort of proxying wouldn't handle very well (though, I think Castle has a method interceptor of some sort). Concrete types get rid of any sort of assumption there; the only real requirement is new().

Update:

Here's a sample creating the type from a string name, and also showing an invalid type:

using System;
using Microsoft.Practices.Unity;
using Newtonsoft.Json;

namespace TestGrounds
{
    public class TestClass
    {
        #region Properties

        public int TestIntegerProperty { get; set; }

        public string TestStringProperty { get; set; }

        #endregion
    }

    public class BadTestClass : TestClass
    {
        #region Properties

        public double TestDoubleProperty { get; set; }

        #endregion

        #region Constructors

        public BadTestClass(double testDouble)
        {
            TestDoubleProperty = testDouble;
        }

        #endregion
    }

    internal class Program
    {
        #region Static Methods

        private static void Main(string[] args)
        {
            const string json =
                @"{ TestIntegerProperty: 1, TestStringProperty: 'Hello', AnotherTestPropertyToIgnore: 'Sup' }";
            var type = Type.GetType("TestGrounds.TestClass", true);
            var badType = Type.GetType("TestGrounds.BadTestClass", true);

            registerDependencyFromJson(type, json);
            try
            {
                registerDependencyFromJson(badType, json);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadKey();
        }

        private static void registerDependencyFromJson(Type type, string json)
        {
            // type requires a default constructor for this to work
            var constructor = type.GetConstructor(Type.EmptyTypes);
            if(constructor == null)
            {
                throw new ArgumentException("Type must have a parameterless constructor.");
            }

            var deserializedObject = JsonConvert.DeserializeObject(json, type);
            var container = new UnityContainer();

            container.RegisterInstance(type, type.Name, deserializedObject, new ContainerControlledLifetimeManager());
        }

        #endregion
    }
}
2
jbtule On

ActLike is meant for static typing dynamic objects, it requires at minimum an implicit cast to the interface. Instead use DynamicActLike and it will return the final instance without requiring the static cast first.

var interfaceInstance = Impromptu.DynamicActLike(expando, interfaceType);