Home > .Net, C#, Sitecore 6, Unit Testing > Sitecore MVC Ninject controller

Sitecore MVC Ninject controller

This is last post in the series of three. As promised in my last post we will inject a repository through the construct in the controller. To do this we will use the Ninject as our IOC container, This will be a very quick walkthrough.

Also we to make it a bit easier to find controller that needs to have class’ injected we will create a derived class from the controller class’,

    public class NinjectController : Controller
    {}

Some of the assmeblies in sitecore mvc solution gives and error when trying to reflect upon them so there are a couple of catch’s in the code to handle some minor bugs.

Next we will create our controller factory  wrapping the Sitecores standard factory. This new Ninject controller factory will for the controller that should be loaded is of type nijectcontroller instantiate the corresponding concrete type of the constructers parameters.. Also this class loads all the ninject binding. Maybe it’s not so nice that it has multiple responsibility but it is left out for an exercise to refactor the code so it conforms to SRP 🙂 .

public class NinjectInitializeControllerFacotry
{
  public virtual void Process(PipelineArgs args)
  {
    this.SetControllerFactory(args);
  }

  protected virtual void SetControllerFactory(PipelineArgs args)
  {
    NinjectControllerFactory controllerFactory = new     NinjectControllerFactory(ControllerBuilder.Current.GetControllerFactory());
    ControllerBuilder.Current.SetControllerFactory(controllerFactory);
  }
}

and the controller factory it self

  public class NinjectControllerFactory : SitecoreControllerFactory
  {
    public NinjectControllerFactory(IControllerFactory innerFactory) : base(innerFactory)
    {
    }

    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
      Assert.ArgumentNotNull(requestContext, "requestContext");
      Assert.ArgumentNotNull(controllerName, "controllerName");
      try
      {
         IController controller = ICreateController(requestContext, controllerName);
         return controller;
      }
      catch (Exception exception)
      {
        if (!MvcSettings.DetailedErrorOnMissingController)
        {
          throw;
        }
        return new ErrorHandlingController(controllerName, exception);
     }
   }

   public override void ReleaseController(IController controller)
   {
    var disposable = controller as IDisposable;

    if (disposable != null)
    {
      disposable.Dispose();
    }
  }

  private IController ICreateController(RequestContext requestContext, string controllerName)
  {
    string fullControllerName = string.Format("{0}Controller", controllerName);
    Type ninjectController = FindSpecificNinjectController(fullControllerName);
    if (ninjectController != null)
    {
      return (IController)Kernel.Get(ninjectController);
    }
    return base.CreateController(requestContext, controllerName);
  }

  private Type FindSpecificNinjectController(string controllerName)
  {
    IEnumerable<Type> ninjectControllers = GetNinjectControllers();
    if(ninjectControllers.Any())
    {
     Type selectedNincjectController = ninjectControllers.FirstOrDefault(controller => controller.Name == controllerName);
     if (selectedNincjectController != null)
     return selectedNincjectController;
   }
   return null;
  }

 private IEnumerable<Type> GetNinjectControllers()
 {
    List<Type> types = new List<Type>();
    foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
    types.AddRange(FindDerivedTypes(assembly, typeof(NinjectController)));
    return types;
 }
 private IEnumerable<Type> FindDerivedTypes(Assembly assembly, Type baseType)
 {
   try
   {
    return assembly.GetTypes().Where(t => baseType.IsAssignableFrom(t));
   }
   catch (Exception)
   {
    return new List<Type>();
 }
 }

  private static IKernel _kernel;
  private IKernel Kernel
  {
   get
   {
    if (_kernel == null)
      CreateKernel();
     return _kernel;
    }
 }

protected void CreateKernel()
 {
   _kernel = new StandardKernel();
    foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
   {
    if (HasNinjectModules(assembly))
    _kernel.Load(assembly);
  }
 }

public static bool HasNinjectModules(Assembly assembly)
 {
   Type baseType = typeof (NinjectModule);
   try
   {
    return assembly.GetTypes().Any(t => baseType.IsAssignableFrom(t));
   }
  catch (Exception)
  {
    return false;
  }
  }
}

So all that is left is to overwrite the standard controller factory.


<initialize>
 <processor type="SitecorePresentation.SitecoreMVCRoutes,SitecorePresentation">
 <mvcIgnoreHomePage>false</mvcIgnoreHomePage>
 </processor>
 <processor type="Sitecore.Mvc.Pipelines.Loader.InitializeGlobalFilters, Sitecore.Mvc"/>
 <processor type="SitecoreMVCNinjectExtension.NinjectInitializeControllerFacotry, SitecoreMVCNinjectExtension"/>
 <!--<processor type="Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory, Sitecore.Mvc"/>-->
 <processor type="Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc"/>
 </initialize>

Next we can rewrite the controller from the last episode to conform to the new ninjectcontroller


public class NinjectItemController : NinjectController
 {
 private readonly IChildItemRepository _childItemRepository;
 public NinjectItemController(IChildItemRepository childItemRepository)
 {
 _childItemRepository = childItemRepository;
 }

public JsonResult Index(string id)
 {
 return Json(CreateObject(_childItemRepository.Get(id)), JsonRequestBehavior.AllowGet);
 }

&nbsp;

private IEnumerable<object> CreateObject(IEnumerable<Item> items)
 {
 List<object> objectList = new List<object>();
 foreach (Item item in items)
 objectList.Add(new { item.Name });
 return objectList;
 }

}

The repositories from the last post looks the samen but are listed below.

</pre>
public interface IChildItemRepository
{
IEnumerable<Item> Get(string parentItemId);
}
<pre>
</pre>
public class ChildItemRepository : IChildItemRepository
{
public virtual IEnumerable<Item> Get(string parentItemId)
{
ID parentId;
if(ID.TryParse(parentItemId,out parentId))
return GetChildNodes(parentId);
return new List<Item>();
}

private IEnumerable<Item> GetChildNodes(ID parentId)
{
Item parentItem = Sitecore.Context.Database.GetItem(parentId);
return parentItem.GetChildren();
}
}

Now to ninject magic the ninject module that factory are loading bindings from.


public class ChildItemRepositoryNinjectModule : NinjectModule
{
public override void Load()
{
Bind<IChildItemRepository>().To<ChildItemRepository>();

}
}

And of course now we can rewrite out unittest to test the new controller.


public class ItemControllerTest
 {
 [Test]
 public void ItemControllWithNoIdShouldReturnEmptyList()
 {
 IChildItemRepository childItemRepositoryStub = Substitute.For<IChildItemRepository>();
 childItemRepositoryStub.Get(Arg.Any<string>()).Returns(new List<Item>());
 NinjectItemController controller = new NinjectItemController(childItemRepositoryStub);

 JsonResult result = controller.Index(string.Empty);
 Assert.That(result.Data,Is.Empty);
 }

[Test]
 public void ItemControllWithInvalideIDShouldReturnEmptyList()
 {

IChildItemRepository childItemRepositoryStub = Substitute.For<IChildItemRepository>();
 childItemRepositoryStub.Get(Arg.Any<string>()).Returns(new List<Item>());
 NinjectItemController controller = new NinjectItemController(childItemRepositoryStub);
 JsonResult result = controller.Index("invalidID");
 Assert.That(result.Data, Is.Empty);
 }

[Test]
 public void ItemControllWithValidIdShouldReturnJsonListWithItemNames()
 {
 IChildItemRepository childItemRepositoryStub = Substitute.For<IChildItemRepository>();
 List<Item> childList = new List<Item>();

 childList.Add(new ItemStub("stub-a"));
 childList.Add(new ItemStub("stub-b"));
 childList.Add(new ItemStub("stub-c"));
 childList.Add(new ItemStub("stub-d"));

Guid itemGuid = Guid.NewGuid();
 childItemRepositoryStub.Get(itemGuid.ToString()).Returns(childList);
 NinjectItemController controller = new NinjectItemController(childItemRepositoryStub);
 JsonResult result = controller.Index(itemGuid.ToString());

List<object>resultData = (List<object>) result.Data; ;
 Assert.AreEqual(4,resultData.Count());
 Assert.AreEqual(resultData[0].ToString(),"{ Name = stub-a }");
 Assert.AreEqual(resultData[1].ToString(), "{ Name = stub-b }");
 Assert.AreEqual(resultData[2].ToString(), "{ Name = stub-c }");
 Assert.AreEqual(resultData[3].ToString(), "{ Name = stub-d }");
 }
 }

The import thing about the rewrite of the test are the ugly SET on the itemctroller is removed as the repository is passed in as contructor argument.The result from the test

As a last service the image below shows the file structure in visual studio .

Thats all, now we can create easy to test controllers using a repository pattern.

Advertisements
  1. 17/09/2012 at 15:47

    I am right now working on a replacement of the controller factory as well to check for the existence of a controller (in order to allow route patterns such as {controller}/{action}/{id}) rather than throwing an error if no such controller exists (such as for item URls that do not map to controller names). I have hit a problem: because Sitecore supports nested controllers (a page generated with a controller can include controller renderings), Sitecore apparently uses a stack to determine the outermost controller. So when I replace the controller factory, I have to manage this stack. Otherwise, I might not get mvc.requestEnd, which breaks the Page Editor and Preview. I can work around this, but I wonder if you faced any issue like this, and/or if you tested the Page Editor/Preview, nested controllers, items that map to controllers instead of specifying views in layout details, items that specify Web Forms instead of MVC components in layout details, or routes such as {controller}/{action}/{id} after implementing this controller? This may be totally irrelevant in your case.

  2. 24/09/2012 at 05:08

    I followed your setup but find that NinjectControllerFactory. CreateController never seem to be called. The constructor is called. ( I put Sitecore Diagnosis logging in both places.) Any idea what’s going on?

    The code are exactly the same and I did comment out Sitecore controller factory in Sitecore.Mvc.config. Sitecore version is 6.6.

    Thanks!

    • 24/09/2012 at 09:36

      Hi
      It could be a couple of things.
      Do the controller derives from the ninject controller class ?
      Have added the the route for the controller as descriped in the first post in this series or at John Wests blog ?
      Does the controller load at all ?

      Thomas

      • 03/10/2012 at 19:01

        Hi Thomas, sorry for the delay in getting back..my colleague and I have figured out what’s happening: We create Controller Rendering (not a generic controller like in your example but doesn’t really matter), and had wrong names in the rendering definition. We were putting full assembly and class in controller name, which seem to force Sitecore use reflection at Presentation rendering time. Once the name is shortened, Ninject.web.mvc does intercept the controller creation and all is working.

        So our overall setup is, let Ninject.mvc package http://nuget.org/packages/Ninject.MVC3 attach App start code to Global.ascx.cs, bind the controller constructor dependencies there. Then controller creation seem to work without overwriting. Still, your post has been very useful and thanks for the help.

      • 03/10/2012 at 20:03

        Hi no problem.
        Yes i know about the ninjectmvc but I just dont like using the “ninject application” in global.asax. I rather use the pipelines provided by Sitecore. But none the less you got it to work for you which is the most important thing.

        I have a post with the ninjectmvc here ninjectmvc

    • 29/09/2012 at 20:27

      Sorry i found the error there where missing an “override” on IController CreateController in the create controller class

  1. 24/08/2013 at 17:48

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

%d bloggers like this: