Skip to content

Context Variables in Ninject

March 13, 2008

Michael Hart asked a very good question on the Ninject user group. Specifically, he was looking for a simple way to integrate Ninject into the ASP.NET MVC IControllerFactory, to use the kernel to activate controllers when they were requested. He was doing it by creating a custom context. While this is a very creative solution, it’s really just a workaround, and gets messy quickly.

Michael’s question made me realize that Ninject’s contextual binding system is focused primarily on information that is known at compile-time, and doesn’t provide much support for information not known until run-time. To fix that problem, I’ve enhanced the transient parameters feature that I announced yesterday to include context variables. Simply put, context variables are just values that you define during your call to the kernel’s Get() method. You can then declare conditional bindings that will examine these variables and alter the activation strategy accordingly.

For example, here’s how to use context variables to activate controllers for ASP.NET MVC. The magic happens in the CreateController method of the IControllerFactory:

public IController CreateController(RequestContext context, string controllerName);

The controller name maps one-to-one with a controller type, which should be activated by the Ninject kernel. In order to let the kernel know which controller to activate, we’ll pass the controller name in as a context variable:

public IController CreateController(RequestContext context, string controllerName) {
  return Kernel.Get<IController>(
    With.Parameters
      .ContextVariable(“controllerName”, controllerName)
 );
}

Now the controller name is available in our activation context. Now we can declare bindings in a module that select the correct controller:

public class ControllerModule : StandardModule {
  public override Load() {
    Bind<IController>().To<HomeController>().Only(
      When.ContextVariable(“controllerName”).EqualTo(“home”)
    );
    Bind<IController>().To<UserController>().Only(
      When.ContextVariable(“controllerName”).EqualTo(“user”)
    );
  }
}

Now, life is good, but we can do better. Having to declare a binding for each controller is a little cumbersome. Here’s a quick-and-dirty way to automatically register all controllers in certain assemblies. It’s based off the DefaultControllerFactory in MonoRail:

public class ControllerModule : StandardModule
{
  private IEnumerable<Assembly> _assemblies;

  public ControllerModule(params Assembly[] assemblies)
  {
    _assemblies = assemblies;
  }

  public override void Load()
  {
    foreach (Assembly assembly in _assemblies)
    {
      IDictionary<string, Type> controllers = FindControllers(assembly);

      foreach (KeyValuePair<string, Type> entry in controllers)
      {
        string name = entry.Key;
        Type type = entry.Value;

        Bind(typeof(IController)).To(type).Only(
          When.ContextVariable(“controllerName”).EqualTo(name)
        );
      }
    }
  }

  private IDictionary<string, Type> FindControllers(Assembly assembly)
  {
    Dictionary<string, Type> controllers = new Dictionary<string, Type>();

    foreach (Type type in assembly.GetExportedTypes())
    {
      if (!type.IsPublic || type.IsAbstract || type.IsInterface || type.IsValueType)
        continue;

      if (typeof(IController).IsAssignableFrom(type))
        controllers.Add(GetNameForController(type), type);
    }

    return controllers;
  }

  private string GetNameForController(Type type)
  {
    if (type.Name.EndsWith(“Controller”))
      return type.Substring(0, name.IndexOf(“Controller”));
    else
      return type.Name;
  }
}

To use this, just load the assemblies when you create the module, and pass them in to the constructor:

Assembly assembly = Assembly.Load(“MySite.Controllers”);
IKernel kernel = new StandardKernel(
  new ControllerModule(assembly)
);

Disclaimer! I scribbled this code in Notepad, so it may or may not be exactly correct. It may also melt your monitor, thereby singing your eyebrows off. Use at your own risk! :)

These enhancements have been committed to the trunk, so feel free to tinker and/or try to break them. I’m going to continue to work to improve the flexibility of Ninject’s binding logic and make run-time information more accessible to the contextual binding system. If anyone has any thoughts, I’d love to hear them!

About these ads

From → miscellaneous

8 Comments
  1. How Lun permalink

    Perfect, this is what I have been looking for the past 3 days! Thanks.

  2. How Lun permalink

    There are errors in the code:

    No ContextVariable found under Ninject.Core.Parameters.With class:
    With.Parameters.ContextVariable(“controllerName”, controllerName)

    I use the below one:
    With.Parameters.Variable(“controllerName”, controllerName)

    Same to:
    When.ContextVariable(“controllerName”).EqualTo(“user”)

    I use:
    When.Context.Variable(“controllerName”).EqualTo(“user”)

    Cheers.

  3. @How: You’re right, this post shows the old syntax, which has changed in the latest trunk build (which will become Ninject 1.5).

  4. I have a [very] small suggestion. How about creating a little more syntactic sugar. Currently your Get(Type) method returns an object, how about creating the following methods:

    GetAs(Type type) where T : object
    … Other ones similar to Get() or Get(Type)

    GetCastedTo(Type type) // Attempts a cast and will not handle the exception if it occurs.
    … Other ones similar to Get() or Get(Type)

  5. Oops. I meant to type GetAs(Type) where T : class

  6. And I just noticed my angle brackets were stripped. Here we go again:

    GetAs<T>(Type) where T : class
    GetCastedTo<T>(Type)

  7. If you don’t need to change the default behavior for looking up controller types by name, there’s a simpler option. Simply have your controller factory inherit from DefaultControllerFactory. Then in your CreateController method:

    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
    IController controller = null;
    Type controllerType = base.GetControllerType(controllerName);
    if (controllerType != null) {
    controller = this.Kernel.Get(controllerType, new IParameter[0]) as IController;
    }
    return controller;
    }

    Note that I’m simply calling into DefaultController’s implementation for looking up the controller type based on the controller name. Once I have the type, then I call into Ninject. Saves a lot of work.

Trackbacks & Pingbacks

  1. Scott Hanselman's Computer Zen - List of .NET Dependency Injection Containers (IOC)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: