Question

I want to avoid ctors that take a large number of repository interfaces.

Instead, I want to have a single ctor parameter: a typed repository set.

By typed, I mean a class with a bunch of properties for each repository that the enclosing class uses. Something like this:

public sealed class MyRepositorySet
{
    public IUserRepository UserRepository { get; set; }

    public IOtherRepository OtherRepository { get; set; }
}

I want to use Ninject to automatically create such a repository set, populate each of the properties by creating an instance of each repository, and then create the dependent class with the initialized repository set.

I know this can be done, but I can't find an example. I don't think I know what a typed repository set is properly called.

Was it helpful?

Solution 2

I actual answered my own question. Ninject had everything I needed to do the job, just not out-of-the-box. After calling Bind() an instance of IBindingToSyntax is returned. It has a method called WithPropertyValue. This method takes a value or a callback. The callback has an instance of Ninject.Activation.IContext, which has a IKernel, which finally has the Get method. So... I can look at the type of the property named in WithPropertyValue, grab the property, determine the type and then have the callback Get an instance for the property type. Phew.

Here is an extension class I wrote to help in populating my typed repository sets:

using System;
using System.Linq.Expressions;
using System.Reflection;
using Ninject;
using Ninject.Activation;
using Ninject.Planning.Targets;
using Ninject.Syntax;

namespace NinjectExtensions
{
    /// <summary>
    /// Provides extension methods for the BindingWithSyntax class.
    /// </summary>
    public static class BindingWithSyntaxExtensions
    {
        /// <summary>
        /// Indicates that the specified property should be injected with the bound type of the property.
        /// </summary>
        /// <typeparam name="TBinding">The type of the object to set the property for.</typeparam>
        /// <param name="instance">Used to add additional information to a binding.</param>
        /// <param name="name">The name of the property.</param>
        /// <returns>The instance that was passed in.</returns>
        public static IBindingWithSyntax<TBinding> WithPropertyValue<TBinding>(this IBindingWithSyntax<TBinding> instance, string name)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            PropertyInfo propertyInfo = typeof(TBinding).GetProperty(name);
            if (propertyInfo == null)
            {
                throw new ArgumentException("There was not a public property with the given name.", "name");
            }
            Func<IContext, object> callback = context => context.Kernel.Get(propertyInfo.PropertyType);
            return instance.WithPropertyValue(name, callback);
        }

        /// <summary>
        /// Indicates that the specified property should be injected with the bound type of the property.
        /// </summary>
        /// <typeparam name="TBinding">The type of the object to set the property for.</typeparam>
        /// <typeparam name="T">The type of the property.</typeparam>
        /// <param name="instance">Used to add additional information to a binding.</param>
        /// <param name="propertyGetter">An expression yielding the property to set the value to.</param>
        /// <returns>The instance that was passed in.</returns>
        public static IBindingWithSyntax<TBinding> WithPropertyValue<TBinding, T>(this IBindingWithSyntax<TBinding> instance, Expression<Func<TBinding, T>> propertyGetter)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            if (propertyGetter == null)
            {
                throw new ArgumentNullException("propertyGetter");
            }
            PropertyInfo propertyInfo = getPropertyInfo(typeof(TBinding), propertyGetter);
            Func<IContext, object> callback = context => context.Kernel.Get<T>();
            return instance.WithPropertyValue(propertyInfo.Name, callback);
        }

        /// <summary>
        /// Indicates that the specified property should be injected with the given value.
        /// </summary>
        /// <typeparam name="TBinding">The type of the object to set the property for.</typeparam>
        /// <typeparam name="T">The type of the property.</typeparam>
        /// <param name="instance">Used to add additional information to a binding.</param>
        /// <param name="propertyGetter">An expression yielding the property to set the value to.</param>
        /// <param name="value">The value to set the property to.</param>
        /// <returns>The instance that was passed in.</returns>
        public static IBindingWithSyntax<TBinding> WithPropertyValue<TBinding, T>(this IBindingWithSyntax<TBinding> instance, Expression<Func<TBinding, T>> propertyGetter, T value)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            if (propertyGetter == null)
            {
                throw new ArgumentNullException("propertyGetter");
            }
            PropertyInfo propertyInfo = getPropertyInfo(typeof(TBinding), propertyGetter);
            return instance.WithPropertyValue(propertyInfo.Name, value);
        }

        /// <summary>
        /// Indicates that the specified property should be injected with the value returned by the callback.
        /// </summary>
        /// <typeparam name="TBinding">The type of the object to set the property for.</typeparam>
        /// <typeparam name="T">The type of the property.</typeparam>
        /// <param name="instance">Used to add additional information to a binding.</param>
        /// <param name="propertyGetter">An expression yielding the property to set the value to.</param>
        /// <param name="callback">A function to call to retrieve the value to set the property to.</param>
        /// <returns>The instance that was passed in.</returns>
        public static IBindingWithSyntax<TBinding> WithPropertyValue<TBinding, T>(this IBindingWithSyntax<TBinding> instance, Expression<Func<TBinding, T>> propertyGetter, Func<IContext, T> callback)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            if (propertyGetter == null)
            {
                throw new ArgumentNullException("propertyGetter");
            }
            if (callback == null)
            {
                throw new ArgumentNullException("callback");
            }
            PropertyInfo propertyInfo = getPropertyInfo(typeof(TBinding), propertyGetter);
            Func<IContext, object> baseCallback = context => callback(context);
            return instance.WithPropertyValue(propertyInfo.Name, baseCallback);
        }

        /// <summary>
        /// Indicates that the specified property should be injected with the value returned by the callback.
        /// </summary>
        /// <typeparam name="TBinding">The type of the object to set the property for.</typeparam>
        /// <typeparam name="T">The type of the property.</typeparam>
        /// <param name="instance">Used to add additional information to a binding.</param>
        /// <param name="propertyGetter">An expression yielding the property to set the value to.</param>
        /// <param name="callback">A function to call to retrieve the value to set the property to.</param>
        /// <returns>The instance that was passed in.</returns>
        public static IBindingWithSyntax<TBinding> WithPropertyValue<TBinding, T>(this IBindingWithSyntax<TBinding> instance, Expression<Func<TBinding, T>> propertyGetter, Func<IContext, ITarget, T> callback)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            if (propertyGetter == null)
            {
                throw new ArgumentNullException("propertyGetter");
            }
            if (callback == null)
            {
                throw new ArgumentNullException("callback");
            }
            PropertyInfo propertyInfo = getPropertyInfo(typeof(TBinding), propertyGetter);
            Func<IContext, ITarget, object> baseCallback = (context, target) => callback(context, target);
            return instance.WithPropertyValue(propertyInfo.Name, baseCallback);
        }

        private static PropertyInfo getPropertyInfo<T>(Type bindingType, Expression<T> expression)
        {
            if (expression.Body.NodeType != ExpressionType.MemberAccess)
            {
                throw new ArgumentException("The expression did access a property.", "propertyGetter");
            }
            MemberExpression memberExpression = (MemberExpression)expression.Body;
            if (memberExpression.Member.MemberType != MemberTypes.Property)
            {
                throw new ArgumentException("The expression did not access a property.", "propertyGetter");
            }
            PropertyInfo propertyInfo = (PropertyInfo)memberExpression.Member;
            if (!propertyInfo.DeclaringType.IsAssignableFrom(bindingType))
            {
                throw new ArgumentException("The expression did not access a property in the specified type.", "propertyGetter");
            }
            return propertyInfo;
        }

        /// <summary>
        /// Injects every property with the bound type of the property.
        /// </summary>
        /// <typeparam name="TBinding">The type of the object to set the property for.</typeparam>
        /// <param name="instance">Used to add additional information to a binding.</param>
        /// <returns>The instance that was passed in.</returns>
        public static IBindingWithSyntax<TBinding> SetAllProperties<TBinding>(this IBindingWithSyntax<TBinding> instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            IBindingWithSyntax<TBinding> result = instance;
            foreach (PropertyInfo property in typeof(TBinding).GetProperties())
            {
                PropertyInfo local = property;
                result = result.WithPropertyValue(local.Name, context => context.Kernel.Get(local.PropertyType));
            }
            return result;
        }
    }
}

This allows me to configure a typed repository set like this:

Bind<IMyRepository>().To<MyRepository>();
Bind<MyRespositorySet>().ToSelf()
                        .WithPropertyValue(set => set.MyRepository);

I also took some time to create a Module builder to save me from needing to create a separate Module class for each unit test I write. But that is a pretty big chunk of code, so I'll leave it out.

OTHER TIPS

I think you are completely on the wrong track. Instead of implementing a workaround to reduce the number of constructor arguments you should better fix your real problem which is that your class most likely does not follow the single responsibility principle. In most cases where a class needs a lot of dependencies the single responsibility principle is violated.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top