Ninject.MVC3, Pass DependencyResolver to service-layer?
Asked Answered
F

1

10

In a MVC3-application with Ninject.MVC 2.2.0.3 (after merge), instead of injecting repostories directly into controllers I'm trying to make a service-layer that contain the businesslogic and inject the repostories there. I pass the ninject-DependencyResolver to the service-layer as a dynamic object (since I don't want to reference mvc nor ninject there). Then I call GetService on it to get repositories with the bindings and lifetimes I specify in NinjectHttpApplicationModule. EDIT: In short, it failed.

How can the IoC-container be passed to the service-layer in this case? (Different approaches are also very welcome.)

EDIT: Here is an example to illustrate how I understand the answer and comments.

I should avoid the service locator (anti-)pattern and instead use dependency injection. So lets say I want to create an admin-site for Products and Categories in Northwind. I create models, repositories, services, controllers and views according to the table-definitions. The services call directly to the repositories at this point, no logic there. I have pillars of functionality and the views show raw data. These bindings are configured for NinjectMVC3:

    private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<ICategoryRepository>().To<CategoryRepository>();
        kernel.Bind<IProductRepository>().To<ProductRepository>();
    }       

Repository-instances are created by ninject via two layers of constructor injection, in the ProductController:

private readonly ProductsService _productsService;
public ProductController(ProductsService productsService)
{
    // Trimmed for this post: nullchecks with throw ArgumentNullException 
    _productsService = productsService;
}

and ProductsService:

protected readonly IProductRepository _productRepository;
public ProductsService(IProductRepository productRepository)
{
    _productRepository = productRepository;
}

I have no need to decouple the services for now but have prepared for mocking the db.
To show a dropdown of categories in Product/Edit I make a ViewModel that holds the categories in addition to the Product:

public class ProductViewModel
{
    public Product Product { get; set; }
    public IEnumerable<Category> Categories { get; set; }
}

The ProductsService now needs a CategoriesRepository to create it.

    private readonly ICategoryRepository _categoryRepository;

    // Changed constructor to take the additional repository
    public ProductsServiceEx(IProductRepository productRepository, 
        ICategoryRepository categoryRepository)
    {
        _productRepository = productRepository;
        _categoryRepository = categoryRepository;
    }

    public ProductViewModel GetProductViewModel(int id)
    {
        return new ProductViewModel
                   {
                       Product = _productRepository.GetById(id),
                       Categories = _categoryRepository.GetAll().ToArray(),
                   };
    }

I change the GET Edit-action to return View(_productsService.GetProductViewModel(id)); and the Edit-view to show a dropdown:

@model Northwind.BLL.ProductViewModel
...
    @Html.DropDownListFor(pvm => pvm.Product.CategoryId, Model.Categories
        .Select(c => new SelectListItem{Text = c.Name, Value = c.Id.ToString(), Selected = c.Id == Model.Product.CategoryId}))

One small problem with this, and the reason I went astray with Service Locator, is that none of the other action-methods in ProductController need the categories-repository. I feel it's a waste and not logical to create it unless needed. Am I missing something?

Forgat answered 28/2, 2011 at 23:47 Comment(2)
Related: #2386987Teazel
Thanks. I didn't realise I described Service Locator.Forgat
A
14

You don't need to pass the object around you can do something like this

// global.aspx


 protected void Application_Start()
        {
            // Hook our DI stuff when application starts
            SetupDependencyInjection();
        }

        public void SetupDependencyInjection()
        {         
            // Tell ASP.NET MVC 3 to use our Ninject DI Container
            DependencyResolver.SetResolver(new NinjectDependencyResolver(CreateKernel()));
        }

        protected IKernel CreateKernel()
        {
            var modules = new INinjectModule[]
                              {
                                 new NhibernateModule(),
                                 new ServiceModule(),
                                 new RepoModule()
                              };

            return new StandardKernel(modules);
        }

So in this one I setup all the ninject stuff. I make a kernal with 3 files to split up all my binding so it is easy to find.


In my service layer class you just pass in the interfaces you want. This service class is in it's own project folder where I keep all my service layer classes and has no reference to the ninject library.

// service.cs

    private readonly IRepo repo;
    // constructor
        public Service(IRepo repo)
        {
            this.repo = repo;
        }

This is how my ServiceModule looks like(what is created in the global.aspx)

// ServiceModule()
 public class ServiceModule : NinjectModule
    {
        public override void Load()
        {

           Bind<IRepo>().To<Repo>();


        }

    }       

Seee how I bind the interface to the repo. Now every time it see that interface it will automatically bind the the Repo class to it. So you don't need to pass the object around or anything.

You don't need worry about importing .dll into your service layer. For instance I have my service classes in their own project file and everything you see above(expect the service class of course) is in my webui project(where my views and global.aspx is).

Ninject does not care if the service is in a different project since I guess it is being referenced in the webui project.

Edit

Forgot to give you the NinjectDependecyResolver

   public class NinjectDependencyResolver : IDependencyResolver
    {
        private readonly IResolutionRoot resolutionRoot;

        public NinjectDependencyResolver(IResolutionRoot kernel)
        {
            resolutionRoot = kernel;
        }

        public object GetService(Type serviceType)
        {
            return resolutionRoot.TryGet(serviceType);
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return resolutionRoot.GetAll(serviceType);
        }
    }
Algid answered 28/2, 2011 at 23:53 Comment(10)
Thanks for a very thorough answer! So instead of creating repositories at will inside the services either make sure all methods in a service use the same repositories or don't worry about creating unneeded repositories?Forgat
Please do not use the custom DependencyResolver and Application_Start from this answer. Stay with Ninject.MVC3! Just implement the constructor injection as described above. As described there is no need to use Ninject as service locator.Sheeran
@Remo Gloor - Why not use the DependencyResolver. I mean they built in into mvc 3.0 why use a stand alone plugin? Some more info would be nice about why use Ninject.MVC3 over it. I am always looking for the best ways to do it. But I want to know why one way is better then the other.Algid
@Forgat - I am not sure if I quite understand what your saying but usually I will use multiple repositories. I try to do a repository per db table but you can group common ones needed. So if say I have a ProductService layer I would probably have a ProductRepo but I might also have a AccountRepo as maybe I need to get the users information. So just add another binding to and then add another parameter to your constructor Service(IRepo,IAccountRepo,IProductRepo)Algid
@ Remo Gloor - I might start a new question about why to use DependencyResolver or Ninject.MVC3 but not tonight anymore. I will do it tmr and post the link in this thread as it's probably beneficial to Aasmund as well.Algid
@Algid - As a starting point I use templates to generate repositories, services, controllers and views from the table-definition, all 1:1. Then I begin adding functionality to the GUI. The Edit-view for a central Entity usually needs data from several other repositories to fill dropdowns and such. With constructor injection (which requires Ninject or other DI-framework) of the service into the controller and repositories into services, the overall dependencies of the controller determine which repositories are created instead of just the dependencies of each individual action.Forgat
@Forgat - I am still not sure if I am following you. Maybe if you edit you question with some example code it will make more sense. For me if my controller needs something it has to ask the service layer for it since I don't think the controller needs to know about the repo. It really should just the service layer for what it needs and render the view for. So for stuff like dropwdowns I call the service layer it brings back all a List of something(say Products) I then use automapper to map it to the a ViewModel and then use the VM in my view to generate a dropdownlsit or wahtever I need.Algid
@Algid - Question edited. I was not able to write clearer within 600 characters.Forgat
@Algid - Ninject.MVC3 does use the DependencyResolver, that's the point. But it hooks into the system using the WebActivator rather than in Global.asax, so it's more scalable and allows you to update the framework and Ninject much more easily. Don't use the custom module method you suggest, it's outdated.Faro
If you need ninject to resolve dependencies in an attribute, then you're pretty much stuck using DependencyResolverCroquet

© 2022 - 2024 — McMap. All rights reserved.