2

I am creating a custom ActionResult for my controllers because I've noticed a lot of repeated code that could be made reusable. It looks something like this:

 public ExtendedViewResult<T> : ActionResult
 {
      protected T Model { get; set; }
      protected IModelExtender<T> Extender { get; set; }

      public ExtendedActionResult(T model, IModelExtender<T> extender)
      {
          this.Model = model;
          this.Extender = extender;
      }
 }

 public class BaseController : Controller
 {
     public ExtendedViewResult<T> ExtendedView<T>(T model)
     {
         // I need to create the result here, but how?
         var result = new ExtendedViewResult<T>(model, ???????);

         return result;
     }
 }

The problem I am having is that I'm not sure how to construct my ExtendedViewResult object. Since the interface is generic I want to use Dependency Injection to get the proper object, but I'm not sure how to do that since I'm constructing the object myself.

I am using Ninject and Ninject.MVC3 and the default Nuget package creates a Bootstrapper class for me, and when I access the Bootstrapper.Kernel property I get the following warning:

Ninject.Web.Mvc.Bootstrapper.Kernel is obsolete. Do not use Ninject as Service Locator.

If I'm not supposed to access the kernel directly, then how can I change my code so that I can get the appropriate concrete class?

EDIT
Here is the ninject bootstrapper code. The only method I added is the GetInstance()

public static class NinjectMVC3 
{
    private static readonly Bootstrapper bootstrapper = new Bootstrapper();

    /// <summary>
    /// Starts the application
    /// </summary>
    public static void Start() 
    {
        DynamicModuleUtility.RegisterModule(typeof(OnePerRequestModule));
        DynamicModuleUtility.RegisterModule(typeof(HttpApplicationInitializationModule));
        bootstrapper.Initialize(CreateKernel);
    }

    /// <summary>
    /// Stops the application.
    /// </summary>
    public static void Stop()
    {
        bootstrapper.ShutDown();
    }

    // I ADDED THIS CODE, EVERYTHING ELSE IS AUTO-GENERATED
    // BY THE NUGET PACKAGE
    public static T GetInstance<T>()
    {
        return bootstrapper.Kernel.Get<T>();
    }

    /// <summary>
    /// Creates the kernel that will manage your application.
    /// </summary>
    /// <returns>The created kernel.</returns>
    private static IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        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)
    {
    }        
}
Dismissile
  • 32,564
  • 38
  • 174
  • 263

2 Answers2

1

Sorry this will be dry on actual code - I've not used ninject much, but other DI containers have this solution, and I'm sure ninject will have this construct as well.

The issue is that you are constructing the object itself. When you're really using a DI container and following IoC, anytime you see the keyword new should be a red flag - and using the container as a service locator is a yellow one.

So how do we get rid of the 'new', since you need a new object? The answer is to have your BaseController take a dependency on a factory that can create an ExtendedViewResult. In Autofac (my container of choice), that would be as simple as having a Func<ExtendedViewResult> injected. I would be surprised if Ninject doesn't have the same. In fact, looks like it does - this ninject wiki page points to this blog post on Ninject.Extensions.Factory.

So that means your code for the Controller could look like this:

public class ConcreteController : BaseController
 {
     private Func<Foo,ExtendedViewResult<Foo>> _factory;
     public BaseController(Func<Foo,ExtendedViewResult<Foo>> factory)
     { 
        _factory = factory;
     }

     public ExtendedViewResult<Foo> Method(Foo model)
     {            
         var result = _factory(model);    
         return result;
     }
 }

If you REALLY want to have a generic method do the creation in your base class, then you will probably need to go the explicit factory interface route from the blog post linked above. With this style code though, you would not need it in most cases, and your controllers explicitly declare the dependencies they need.

Philip Rieck
  • 32,368
  • 11
  • 87
  • 99
  • The problem with passing this into the concrete controller is that I could have 10 different action methods that all have different models. In order to do this I would need to pass in 10 different Func dependencies to my controller, which does not seem like the best option. – Dismissile Feb 13 '12 at 17:16
  • @Dismissile In that case I would reccommend creating a factory yourself. I don't think ninject can autocreate factories based on an open generic, and the constructor parameter just makes it harder. If you do find that it can, please update your question, edit this answer, or answer your own question - I'd love to see the result. My container of choice is AutoFac, and I think it would require some registrationsource wizardry to accomplish this. – Philip Rieck Feb 13 '12 at 17:30
  • @Dismissile regarding your own factory, remember that you can take a dependency on IResolutionRoot and call .Get on that. In the simple case, you could simply take a dependency on IResolutionRoot in your BaseController. This is pretty much using a ServiceLocator, but you're asking for the locator as a dependency rather than finding it yourself. – Philip Rieck Feb 13 '12 at 17:34
  • thanks, this is definitely a good starting point. It looks like ninject has support for factories coming out (never heard of them before since this is my first project using DI/IOC) but it is not out yet. – Dismissile Feb 13 '12 at 17:36
0

You should be using an abstract factory anytime your objects want to create other objects. The abstract factory itself can injected. I've asked a similar question here: Abstract factories when using dependency injection frameworks

It really shouldn't make much of a difference that your factory is generic though.

Community
  • 1
  • 1
giulianob
  • 178
  • 1
  • 9