Ninject : Constructor parameter

2019-02-07 09:56发布

I am using Ninject together with ASP.NET MVC 4. I am using repositories and want to do constructor injection to pass in the repository to one of the controllers.

This is my Repository interface:

public interface IRepository<T> where T : TableServiceEntity
{
    void Add(T item);
    void Delete(T item);
    void Update(T item);
    IEnumerable<T> Find(params Specification<T>[] specifications);
    IEnumerable<T> RetrieveAll();
    void SaveChanges();
}

The AzureTableStorageRepository below is an implementation of IRepository<T>:

public class AzureTableRepository<T> : IRepository<T> where T : TableServiceEntity
{
    private readonly string _tableName;
    private readonly TableServiceContext _dataContext;

    private CloudStorageAccount _storageAccount;
    private CloudTableClient _tableClient;

    public AzureTableRepository(string tableName)
    {
        // Create an instance of a Windows Azure Storage account
        _storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);

        _tableClient = _storageAccount.CreateCloudTableClient();
        _tableClient.CreateTableIfNotExist(tableName);
        _dataContext = _tableClient.GetDataServiceContext();
        _tableName = tableName;
    }

Note the tableName parameter needed because I was using a generic table repository to persist data to Azure.

And finally I have the following controller.

public class CategoriesController : ApiController
{
    static IRepository<Category> _repository;

    public CategoriesController(IRepository<Category> repository)
    {
        if (repository == null)
        {
            throw new ArgumentNullException("repository");
        }

        _repository = repository;
    }

Now I want to inject a repository into the controller. So I have created a module that contains the bindings:

/// <summary>
/// Ninject module to handle dependency injection of repositories
/// </summary>
public class RepositoryNinjectModule : NinjectModule
{
    public override void Load()
    {
        Bind<IRepository<Category>>().To<AzureTableRepository<Category>>();
    }
}

The loading of the module is done in the NinjectWebCommon.cs

/// <summary>
    /// Creates the kernel that will manage your application.
    /// </summary>
    /// <returns>The created kernel.</returns>
    private static IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
        kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

        RegisterServices(kernel);
        return kernel;
    }

    /// <summary>
    /// Load your modules or register your services here!
    /// </summary>
    /// <param name="kernel">The kernel.</param>
    private static void RegisterServices(IKernel kernel)
    {
        // Load the module that contains the binding
        kernel.Load(new RepositoryNinjectModule());

        // Set resolver needed to use Ninject with MVC4 Web API
        GlobalConfiguration.Configuration.DependencyResolver = new NinjectResolver(kernel);
    } 

The DependencyResolver was created because Ninject's DependencyResolver implements System.Web.Mvc.IDependencyResolver and this cannot be assigned to GlobalConfiguration.Configuration of the WebApi Application.

So with all this in place, the Ninject part is actually injecting the right type in the Controller but Ninject cannot inject the tableName parameter in the constructor of AzureTableRepository.

How would I be able to do this in this case? I have consulted a lot of articles and the ninject documentation to see how I could use parameters, but I cannot seem to get it working.

Any help would be appreciated.

2条回答
在下西门庆
2楼-- · 2019-02-07 10:35

Meanwhile I have been playing around with Providers to try and do the trick and it seems to work.

I don't know if this is good idea or if it is overkill but here is what I have done: I have created a generic provider class:

public abstract class NinjectProvider<T> : IProvider
{
    public virtual Type Type { get; set; }
    protected abstract T CreateInstance(IContext context);

    public object Create(IContext context)
    {
        throw new NotImplementedException();
    }

    object IProvider.Create(IContext context)
    {
        throw new NotImplementedException();
    }

    Type IProvider.Type
    {
        get { throw new NotImplementedException(); }
    }
}

And then I implemented that one in the AzureTableRepositoryProvider. (T to support having the same repository for multiple entity types.)

public class AzureTableRepositoryProvider<T> : Provider<AzureTableRepository<T>> where T : TableServiceEntity
{
    protected override AzureTableRepository<T> CreateInstance(IContext context)
    {
        string tableName = "";

        if (typeof(T).Name == typeof(Category).Name)
        {
            // TODO Get the table names from a resource
            tableName = "categories";
        }
        // Here other types will be addedd as needed

        AzureTableRepository<T> azureTableRepository = new AzureTableRepository<T>(tableName);

        return azureTableRepository;
    }
}

By using this provider I can pass in the right table name for the repository to work with. But for me, two questions remain:

  1. Is this good practice or could we do things much simpler?
  2. In the NinjectProvider class I have two notImplementedException cases. How could I resolve these? I used sample code from the following link but that does not work as the Provider is abstract and the code does not have a body for the create method... enter link description here
查看更多
Emotional °昔
3楼-- · 2019-02-07 10:42

I'd use the WithConstructorArgument() method like...

Bind<IRepository<Category>>().To<AzureTableRepository<Category>>()
    .WithConstructorArgument("tableName", "categories");

The rest of the repository design is probably another question. IMHO It seems like a big no no to create a table or do any heavy lifting in a ctor.

查看更多
登录 后发表回答