1

I'm considering trying to use Munq to do property injection of optional dependencies.

Is this possible without doing something like this in the injected class?:

MunqDependencyResolver.Container.Resolve<TTpe>();

Additionally, is using property injection recommended in this situation (optional dependencies) or are there better alternatives?

Jeff Mitchell
  • 1,459
  • 1
  • 17
  • 33
  • Optional dependencies seems like it could be a sign of a design issue. If an object has an optional dependency I'm guessing it has more than one function and potentially should be split into 2 or more cohesive units. – MattDavey Apr 23 '12 at 11:19

2 Answers2

3

Calling the container from within your code is generally a bad idea. There is a good article about this from Mark Seemann.

Property injection by itself is fine, but should in general only be used in two situations:

  1. The dependency is truly optional, and the application can work correctly when it is missing. Or,
  2. Constructor injection is not possible, for instance because of circular dependencies.

In all other cases, go for constructor injection.

The way to do property injection with Munq is as follows:

container.Register<IDatabase>(c =>
    new Database(c.Resolve<ILogger>())
    {
        // Property injection.
        ErrorHandler = c.Resolve<IErorhandler>()
    });

Note that dependencies should hardly ever be optional. Optional dependencies make the application code more complicated, because this forces the code to differentiate between two types of dependencies (an implementation and a null value) and will lead to extra if-null checks in your code. Most of the time you can simply make the dependency required and add inject/register an empty implementation (Null Object Pattern) instead.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • I agree 100%. In my case I am experimenting with logging and cache providers that I would like to leverage if available, but don't want to affect anything if absent. Also, thank you for the article. – Jeff Mitchell Apr 23 '12 at 15:01
  • 1
    Instead of optionaly injecting those dependencies as properties, better is to use constructor injection and inject Null Objects (empty implementations) when possible. This makes the application logic simpler, since you don't have to do null checks, and makes it much clearer what dependencies a type needs. – Steven Apr 23 '12 at 17:19
  • And while talking about logging, you might find this answer interesting: http://stackoverflow.com/a/9915056/264697. – Steven Apr 23 '12 at 17:23
0

I wanted to give a performance boost to a legacy app, so I came up with this, to get Munq to use the following DepencyAttribute

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class DependencyAttribute : Attribute
{
    // Fields
    private readonly string name;

    /// <summary>
    /// Create an instance of DependencyAttribute with no name
    /// </summary>
    public DependencyAttribute()
        : this(null)
    {
    }

    /// <summary>
    /// Create an instance of DependencyAttribute with name
    /// </summary>
    /// <param name="name"></param>
    public DependencyAttribute(string name)
    {
        this.name = name;
    }

    /// <summary>
    /// The name specified in the constructor
    /// </summary>
    public string Name
    {
        get
        {
            return name;
        }
    }
}


    public static IRegistration LegacyRegister(this IocContainer container, Type from, Type to, string name = null)
    {
        if (from.ContainsGenericParameters)
            container.Register(name, from, to);

        var reg = container.Register(name, from, Create(to));
        return reg;
    }

    public static IRegistration LegacyRegister<TFrom,TTo>(this IocContainer container, string name = null)
    {
        return container.LegacyRegister(typeof (TFrom), typeof (TTo), name);
    }

    public static System.Func<IDependencyResolver, object> Create(Type from)
    {
        var container = Expression.Parameter(typeof(IDependencyResolver), "container");
        var ctor = BuildExpression(from, container);
        var block = InitializeProperties(ctor, container);
        var func = Expression.Lambda<System.Func<IDependencyResolver, object>>(block, new[] { container});

        return func.Compile();
    }

    private static Expression InitializeProperties(NewExpression ctor, ParameterExpression container)
    {
        var expressionList = new List<Expression>();
        var instance = Expression.Variable(ctor.Type, "ret");
        var affect = Expression.Assign(instance, ctor);
        //expressionList.Add(instance);

        expressionList.Add(affect);

        var props = from p in instance.Type.GetProperties(BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.Public)
            let da = p.GetCustomAttributes(typeof (DependencyAttribute), true).Cast<DependencyAttribute>().FirstOrDefault()
            where da != null
            select new {Property = p, da.Name};

        var propsSetters = from p in props
            let resolve = p.Name == null ?
                Expression.Call(container, "Resolve", new[] {p.Property.PropertyType})
                : Expression.Call(container, "Resolve", new[] {p.Property.PropertyType}, Expression.Constant(p.Name, typeof (string)))
            select Expression.Call(instance, p.Property.GetSetMethod(true), resolve);

        expressionList.AddRange(propsSetters.ToList());
        expressionList.Add(instance);

        var block = Expression.Block(ctor.Type, new[] { instance }, expressionList);
        return block;
    }

    private static NewExpression BuildExpression(Type type, ParameterExpression container)
    {
        ConstructorInfo constructorInfo = GetConstructorInfo(type);

        var parameters = from p in constructorInfo.GetParameters()
                    let da = p.GetCustomAttributes(typeof(DependencyAttribute), true).Cast<DependencyAttribute>().FirstOrDefault()
                        ?? new DependencyAttribute()
                    select new { Param = p, da.Name };


        var list = parameters.Select(p => 
            Expression.Call(container, "Resolve", new [] { p.Param.ParameterType },
                p.Name == null ? new Expression[0] : new Expression[] { Expression.Constant(p.Name, typeof(string)) }));

        return Expression.New(constructorInfo, list);
    }

    private static ConstructorInfo GetConstructorInfo(Type implType)
    {
        ConstructorInfo constructorInfo = implType.GetConstructors().OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
        if (constructorInfo == null)
            throw new ArgumentException(string.Format("The requested class {0} does not have a public constructor.", (object)implType));
        return constructorInfo;
    }
Hylaean
  • 1,237
  • 13
  • 19