views:

512

answers:

2

Hello,

I'm designing medium-size website using asp.net mvc technology. All business logic is organized into IServices (like IDomainService, IUserService, IAuthService, ITrainingService). All services are using IRepositories. I'm using Ninject 1.5 to wire services with controllers and it seems working perfectly.

There is so far one subject I have no idea how to handle. Some services create contexts (per request) - for instance IDomainService creates DomainContext (per request) which is needed for IUserService. ITrainingService is used only in TrainingController, which is accessible only by authorized users, and ITrainingService requires UserContext (also per request) to know who is having training.

This is my first project using IoC container. Is there any design pattern or code-schema how to solve it? I think I can fill context object using ActionFilters but how to manage their lifetime and where to put them to be accessible for IServices? (in an ellegant way)

Regards, Tom.

A: 

Im not exactly sure if I understand your problem completely, hopefully this bit of advice can help.

When using an IoC container you let the container handle object lifetime managment. I have only used Castle Windsor and StructureMap for dependency injection so I cant give you a concrete example for how to do this with Ninject.

Looking through the Ninject documentation I think you want to look at Activation Behaviours to specify object lifetime management.

Hope this helps.

Joel Cunningham
Broken link, advice far superseded by Peter's complete answer. (One specifically wrong bit is to suggest activation behaviors (the InRequestScope in Peter's answer is the correct approach)). Suggest deleting this answer. (Not greviously wrong so not -1ing but not far off it)
Ruben Bartelink
+4  A: 

I've used Ninject specifically in an MVC application. The way you'd accomplish this with Ninject is in the configuration or binding of your dependencies. When you do this, you specify how you want your object lifetimes to be managed. In most cases of a web app, you objects will be per request as you've indicated in your question.

One thing I've noticed in your question is that your DomainContext is being created by an IDomainService object and is used by other objects. If the domain service object is a sort of factory for a DomainContext, then you don't have much of a problem -- this becomes an exercise of how you configure Ninject to provide concrete objects and inject dependencies.

Here's general guidance on how you would structure your application -- bear in mind I don't have full understanding of your interfaces and classes:

public class GlobalApplication : NinjectHttpApplication {
  protected override void RegisterRoutes(RouteCollection routes) {

    // Your normal route registration goes here ...

    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default",                                              
        "{controller}/{action}/{id}",                           
        new { controller = "Home", action = "Index", id = "" }  
    );

  }

  // This function is resposible for creating a Ninject kernel.  This is where 
  // the magic starts to happen.  
  protected override IKernel CreateKernel() {
    var modules = new IModule[] {
                                  new AutoWiringModule(),
                                  new AutoControllerModule(
                                        Assembly.GetExecutingAssembly()),
                                  new ServiceModule()
                                };

    return new StandardKernel(modules);
  }
}

Note above that the easiest way to get Ninject to work is to derive your application class from the NinjectHttpApplication class. You will need to change your RegisterRoutes to an override method and will also be required to implement a method called CreateKernel. The CreateKernel method is responsible for returning the Ninject kernel which is itself the IoC container.

In the CreateKernel method, the Ninject-provided AutoControllerModule scans assemblies for MVC controller classes and registers them with the container. What this means is that dependencies on those controllers can now be injected by Ninject as it has become the controller provider for the application. The ServiceModule class is one that you need to create to register all of your services with Ninject. I'm guessing it would look something like this:

internal class ServiceModule : StandardModule {
  public override void Load() {
    Bind<IDomainService>()
      .To<MyDomainService>()
      .Using<OnePerRequestBehavior>();

    Bind<DomainContext>()
      .ToMethod( ctx => ctx.Kernel.Get<IDomainService>().CurrentDomainContext )
      .Using<OnePerRequestBehavior>();

    Bind<IService>()
      .To<MyServiceType>()
      .Using<OnePerRequestBehavior>();
  }
}

Ninject's got a pretty expressive fluent interface for configuration. Note above that each statement basically associates a concrete class with an interface it implements. The "Using" phrase in the statement indicates to the Ninject kernel that the object will live for the life of the request only. So, for example, this means that anytime an IDomainService object is requested from the Ninject kernel during the same request, the same object will be returned.

As for you context objects, I'm taking a stab that your domain service creates these contexts and acts as a factory of sorts. In that regard, I bound instances DomainContext classes above to be produced by getting the value of the a property called CurrentDomainContext off the IDomainService. That's what the lambda above accomplishes. The nice thing about the "ToMethod" binding in Ninject is that you have access to a Ninject activation context object that allows you to resolve objects using the kernel. That's exactly what we do in order to get the current domain context.

The next steps are to ensure your objects accept dependencies properly. For example, you say that ITrainingService is used only in the TrainingController class. So, in that case I would ensure that TrainingController has a constructor that accepts an ITrainingService parameter. In that constructor, you can save the reference to the ITrainingService in a member variable. As in:

public class TrainingController : Controller {
  private readonly ITrainingService trainingService;

  public TrainingController(ITrainingService trainingService) {
    this.trainingService = trainingService;
  }

  // ... rest of controller implementation ...
}

Remember that Ninject has already registered all of your controllers with the Ninject kernel, so when this controller is created and it's actions are invoked, you'll have a reference to the ITrainingService by way of the trainingService member variable.

Hope this helps you out. Using IoC containers can become quite confusing at times. Note, I highly recommend you check out the Ninject documentation -- it's a very well written introduction to Ninject as well as DI/IoC concepts. I've also left out discussion of the AutoWiringModule shown above; however, Nate Kohari (Ninject's creator) has a good write-up on his blog about this feature.

Good luck!

Peter Meyer