Thursday, 12 October 2017

Simple controller dependency injection in ASP.NET MVC

Introduction

While developing real time application in ASP.NET MVC, we might come to a point where we feel the need of injecting an object into ASP.NET MVC controller instance so that we can call methods of that objects in our controller classes. By default ASP.NET MVC creates a controller using parameter less constructor and we will not be able to inject our class into it. Controller dependency injection is one way that let us do that. 

To inject our class into ASP.NET MVC controller, we can follow two approaches
  1. Overriding DefaultControllerFactory class that comes along with ASP.NET MVC Framework
  2. Implementing IControllerFactory interface methods of ASP.NET MVC

Purpose: Controller dependency injection in ASP.NET MVC

The purpose of dependency injection in MVC controller is to achieve following where we are passing the parent class to the constructor of the Controller so that we can use its methods into action methods. In this case, we are using GetName() andGetLoginDetails() method of the Parent class in DefaultControllerFactoryMethod method.

public class HomeController : Controller
    {
        private readonly IParent _iparent;
        public HomeController()
        {

        }

        public HomeController(IParent parent)
        {
            _iparent = parent;
        }

        public ActionResult DefaultControllerFactoryMethod()
        {
            ViewBag.Name = _iparent.GetName();
            ViewBag.LoginDetails = _iparent.GetLoginDetails("MyUsreName");
            return View();
        }

Solution Approach - 1 -  overriding DefaultControllerFactory to achieve dependency injection

In this approach, we create our own controller factory class by inheriting DefaultControllerFactory class that is part of ASP.NET MVC framework and override its GetControllerInstance method.

Look at below code snippet carefully, In GetControllerInstance method, we are instantiating the Parent class that is being passed to the Activator.CreateInstance method only when the controller name is "Home" otherwise the default constructor of the controller is instantiated without any parameter.

public class MyCustomDefaultControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            IParent parent = new Parent();

            // here we can find out the controller name and based on that instantiate class that inherits IParent interface and pass into the CreateInstance method
            var controllername = requestContext.RouteData.Values["controller"].ToString();

            IController controller = null;
            if (controllername == "Home")
            {
                controller = Activator.CreateInstance(controllerType, new[] { parent }) as Controller;
            }
            else
            {
                controller = Activator.CreateInstance(controllerType) as Controller;
            }
            
            return controller;
        }

        public override void ReleaseController(IController controller)
        {
            IDisposable dispose = controller as IDisposable; 
            if (dispose != null)
            {
                dispose.Dispose();
            }
        }
    }

In this case, we have just taken "Home" controller for this example, in real time scenario you can put many such controller names in the If condition or switch condition.

The ReleaseController method simply checks for the controller and if it is disposable, calls the Dispose() method.

Once we have created above controller factory class, we need to register them into Global.asax Application_Start event.

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

        // register default controller factory
         ControllerBuilder.Current.SetControllerFactory(new MyCustomDefaultControllerFactory());

        }

Now, if we call the DefaultControllerFactoryMethod of the Home controller we will get the respective output. The other controller doesn't get affected by this as we have passed the instance of the Parent class only when controllerName is "Home" in the DefaultControllerFactoryMethod class declared above.

Solution Approach 2 - Implementing IControllerFactory interface methods to achieve dependency injection

Create a new class that inherits IControllerFactory interface and implement its methods. In this case we have created MyCustomControllerFactory class that inherits IControllerFactory interface.

First we have implemented CreateController method of this interface that checks for the controller name and return the controller type depending on what we have written in the switch statement.

public class MyCustomControllerFactory : IControllerFactory
    {
        
        public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            IController controller = null;
            Type type = null;
            switch (controllerName)
            {
                case "Account" :
                    type = typeof(AccountController);
                    break;
                case "Manage" :
                    type = typeof(AccountController); // change to see
                    break;
                case "Another":
                    type = typeof(HomeController);
                    break;
                default:
                    type = typeof(HomeController);
                    break;
            }

            // NOTE: can instantiate an interface or class that can be passed in the CreateInstance method
            IParent parent = new Parent();

            controller = (IController)Activator.CreateInstance(type, parent);
            return controller;
        }

        public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            System.Web.SessionState.SessionStateBehavior sessionState = new System.Web.SessionState.SessionStateBehavior();
            switch (controllerName)
            {
                case "Account":
                    sessionState =  System.Web.SessionState.SessionStateBehavior.Default;
                    break;
                case "Manage":
                    sessionState = System.Web.SessionState.SessionStateBehavior.Required;
                    break;
                case "Home":
                    sessionState = System.Web.SessionState.SessionStateBehavior.ReadOnly;
                    break;
                default:
                    sessionState = System.Web.SessionState.SessionStateBehavior.Default;
                    break;
            }
            return sessionState;
        }

        public void ReleaseController(IController controller)
        {
            IDisposable disposeMe = controller as IDisposable;
            if (disposeMe != null)
            {
                disposeMe.Dispose();
            }
        }
    }
Second, we also need to implement GetControllerSessionBehavior method that let us decide what kind of behavior we want for each controller.

Third, ReleaseController as explained above simply calls the Dispose() method of the controller.

As in case of approach 1, we will also need to register this custom controller factory class into Global.asax Application_Start event.

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

           // register the custom controller factory now 
           ControllerBuilder.Current.SetControllerFactory(new MyCustomControllerFactory());

        }
There are the two simple ways of implementing dependency injection in the controller of ASP.NET MVC.  Read how to create custom action invoker in ASP.NET MVV here.


http://www.dotnetfunda.com/articles/show/3178/simple-controller-dependency-injection-in-aspnet-mvc

No comments:

Post a Comment