Life Cycles

This is the story of a way back in… to the rush of moving forward. Born from the Earth’s crust, grown from the seeds of innovation, forged in the fires of industry, the Earth’s most efficient machine creates its most efficient  animal: the bicycle, our noblest invention.

http://www.lifecyclesfilm.com/

Advertisements

A Replacement for Ninject’s WithConstructorArgument Method

When defining DI bindings with Ninject’s fluent syntax one may specify values for constructor arguments of a given type. Supposing that we have a ThingImpl type that has a string argument named prefix, we can write:

kernel.Bind<IThing>()
      .To<ThingImpl>()
      .WithConstructorArgument("prefix", "[TEST]");

You can assign values for different arguments by invoking WithConstructorArgument multiple times. If there’s an error on the argument names or types it will only be caught when there is a request for the service (IThing in this case). Also, Ninject will report that it couldn’t find a binding for some argument, instead of reporting that you’re using a wrong argument name.

Another possible approach is a method that allows you to specify all the arguments (using an anonymous type) and checks if the type’s constructors have matching arguments.

kernel.Bind<IThing>()
      .To<ThingImpl>()
      .WithConstructorArguments(new { prefix = "[PRE]", date = DateTime.UtcNow});

This method can be easily implemented using some reflection goodness:

    public static class BindingWithSyntaxExtensions
    {
        public static IBindingWithOrOnSyntax<T> WithConstructorArguments<T>(
            this IBindingWithSyntax<T> syntax,
            object constructorArguments)
        {
            var args = constructorArguments.GetType().GetProperties();
            if(args.Length == 0)
            {
                throw new ArgumentException("No public properties", "constructorArguments");
            }

            foreach(var prop in args)
            {
                if (typeof(T).GetConstructors()
                    .Any(c => c.GetParameters()
                        .Any(p => p.ParameterType.IsAssignableFrom(prop.PropertyType) && p.Name == prop.Name)))
                {
                    syntax = syntax.WithConstructorArgument(prop.Name, prop.GetValue(constructorArguments, null));
                }
                else
                {
                    throw new InvalidOperationException(String.Format("Constructor argument '{0}' ({1}) not found on {2}", prop.Name, prop.PropertyType, typeof(T).FullName));
                }
            }

            return (IBindingWithOrOnSyntax<T>)syntax;
        }
    }

This will provider feedback when configuring the bindings and alert you if you’re using names/types that don’t match the ones on the type’s constructors.