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.