2011-09-10

ASP .NET MVC and Unity: Navigating through Interfaces

,

In this previous post you saw how i used an IDependencyResolver in combination with an IUnityContainer to created a loosely coupled application. But there is still one dependency that i want to cut and that is the url that is used to navigate.

Normally if we want to navigate to the Index action of our HomeController we use something like this in the url: http://localhost/Home/Index
That is an dependency that i want to cut, because we ask to use a controller of type “HomeController”
.
What i want to do is to navigate through interfaces, http://localhost/IHome/Index for example. When we type this, it will be up to Unity to resolve the requested controller. I will use the solution used in this post as base.

Let’s get started

The first thing that we need to do is to create an IHomeController interface. This interface will be implemented by our HomeController.

IHomeController interface
using System.Web.Mvc;
namespace MVCWebsite.Controllers
{
    public interface IHomeController: IController
    {
        System.Web.Mvc.ActionResult Index();
    }  
}


HomeController
using System.Web.Mvc;
using MVCWebsite.Repository;

namespace MVCWebsite.Controllers
{
    public class HomeController: Controller, MVCWebsite.Controllers.IHomeController
    {
        ITitleRepository _titleRepository = null;

        public HomeController(ITitleRepository titleRepository)
        {
            _titleRepository = titleRepository;
        }

                
        public ActionResult Index()
        {
            ViewBag.Title = _titleRepository.GetTitle("Index");

            return View();
        }             
    }
}

Now that we created an interface, we will register it with our Unitycontainer. This mains i will have to adapt my UnityConfigurator class.

UnityConfigurator
using System.Web.Mvc;
using Microsoft.Practices.Unity;
using MVCWebsite.Controllers;
using MVCWebsite.IoC;
using MVCWebsite.Repository;

namespace MVCWebsite.Ioc
{
    public class UnityConfigurator
    {
        public static IUnityContainer GetContainer()
        {
            IUnityContainer container = new UnityContainer();

            container.RegisterType<IControllerFactory, UnityControllerFactory>();
            container.RegisterType<IHomeController, HomeController>();
            container.RegisterType<ITitleRepository, TitleRepository2>(new HttpContextLifetimeManager<ITitleRepository>());

            return container;
        }
    }
}

When we type an url in our browser the ControllerFactory is used to return an instance of the requested controller. We already use a custom ControllerFactory ( UnityControllerFactory ) so we’ll need to change it’s implementation a bit.

You can override a couple of methods of a ControllerFactory. But we are only interested in 2 of them:

  • GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
  • IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)

The GetControllerType is actually responsible for converting the controllername, that is typed in an url, to a specific type. So we will have to override this method so that we can write our custom logic.

UnityControllerFactory
protected override Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            // use the base method
            Type type = base.GetControllerType(requestContext, controllerName);

            // If the base method can't resolve a controllername, we 'll have to help it a hand.
            if (null == type)
            {
                Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
                foreach (var assembly in assemblies)
                {
                    type = assembly.GetTypes().SingleOrDefault(t => t.Name == String.Format("{0}Controller", controllerName));
                    if (null != type)
                        break;
                }
            }

            return type;
        }


If the base method can’t resolve the requested controller type, we’ll look in the current loaded assemblies to see if we can find the type of the requested controller. I still use the base method so that the default way of navigation still works. If you completely want to navigate through interfaces, you don’t make a call to it’s base method. So when we type /IHome/Index, this method will return IHomeController as type.

Now that we have the requested controller type, we’ll need to create an instance of that controller. That’s why we’ll need to override the GetControllerInstance of our ControllerFactory ( UnityControllerFactory ).

The current DependecyResolver will be used create the requested controller. Remember that the current DependencyResolver is our UnityDependencyResolver and that it will use Unity to create an instance of the requested controller type. When we have an instance of the controller, we’ll just need to change the “controller” value in the routedata. If we don’t to this, MVC will try to resolve the requested view in the IHome folder, instead of the Home folder.

UnityControllerFactory
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            IController result = null;

            if (null != controllerType)
            {
                // resolve the requested controller type through the DependencyResolver
                result = DependencyResolver.Current.GetService(controllerType) as IController;

                if (result != null)
                {
                    // change the route data so that we'll look in the right place to resolve the requested view
                    requestContext.RouteData.Values["controller"] = result.GetType().Name.Replace("Controller", "");
                }
            }

            return result;
        }

 

Let’s test it

So when we type /IHome/Index, we’ll get the index page of the HomeController.

image

Let’s go a step further

It’s common for applications that for certain users you need to display a different page with more/less functionality. Let’s assume that users who are in the role ”Admin” needs to see a different page as their home page. Since we removed the dependency between the url and controller we can easily achieve this functionality.

We create a controller called '”AdminHomeController” which will implement the IHomeController interface. We also create an Index view for the AdminHomeController.

AdminHomeController
using System.Web.Mvc;
using MVCWebsite.Repository;

namespace MVCWebsite.Controllers
{
    public class AdminHomeController : Controller, MVCWebsite.Controllers.IHomeController
    {
        ITitleRepository _titleRepository = null;

        public AdminHomeController(ITitleRepository titleRepository)
        {
            _titleRepository = titleRepository;
        }

        //
        // GET: /Home/
        public ActionResult Index()
        {
            ViewBag.Title = _titleRepository.GetTitle("Index");

            return View();
        }       
    }
}

Now we have to change our UnityConfigurator a little. In Unity is possible to create an “InjectionFactory”. You can link a function with an Injectionfactory. This means that the function will be responsible to return an instance of the requested type.

We’ll create an InjectionFactory for the IHomeController type. This injectionfactory will return an AdminHomeController for users who have an “Admin” role, for all other users a HomeController will be returned.

UnityConfigurator
using System.Web;
using System.Web.Mvc;
using Microsoft.Practices.Unity;
using MVCWebsite.Controllers;
using MVCWebsite.IoC;
using MVCWebsite.Repository;

namespace MVCWebsite.Ioc
{
    public class UnityConfigurator
    {
        public static IUnityContainer GetContainer()
        {
            IUnityContainer container = new UnityContainer();

            container.RegisterType<IControllerFactory, UnityControllerFactory>();
           
            container.RegisterType<IHomeController>(new InjectionFactory(c =>
                                                                {
                                                                    if (HttpContext.Current.User.IsInRole("Admin"))
                                                                    {
                                                                        return (c.Resolve<AdminHomeController>());
                                                                    }
                                                                    else
                                                                    {
                                                                        return (c.Resolve<HomeController>());
                                                                    }
                                                                }));

            container.RegisterType<ITitleRepository, TitleRepository2>(new HttpContextLifetimeManager<ITitleRepository>());

            return container;
        }
    }
}

When a user who is in the “Admin” role types /IHome/Index as url, an instance of AdminHomeController will be returned and he’ll see the Index view from the AdminHome folder.

image

Hope you enjoyed reading it. The solution can be downloaded here.

2 comments:

  1. Though I have some confusion with the codes above, I gotta thank a friend of mine who explained to me throughly this post. He's a computer major and this kind of topic is his forte.

    ReplyDelete