Archives

Archives / 2011 / June
  • A Custom View Engine with Dynamic View Location

        Introduction:

              One of the nice feature of ASP.NET MVC framework is its pluggability. This means you can completely replace the default view engine(s) with a custom one. One of the reason for using a custom view engine is to change the default views location and sometimes you need to change the views location at run-time. For doing this, you can extend the default view engine(s) and then change the default views location variables at run-time.  But, you cannot directly change the default views location variables at run-time because they are static and shared among all requests. In this article, I will show you how you can dynamically change the views location without changing the default views location variables at run-time.

     

        Description:

              Let's say you need to synchronize the views location with controller name and controller namespace. So, instead of searching to the default views location(Views/ControllerName/ViewName) to locate views, this(these) custom view engine(s) will search in the Views/ControllerNameSpace/ControllerName/ViewName folder to locate views.

              First of all create a sample ASP.NET MVC 3 application and then add these custom view engines to your application,

     

        public class MyRazorViewEngine : RazorViewEngine
        {
            public MyRazorViewEngine() : base()
            {
                AreaViewLocationFormats = new[] {
                    "~/Areas/{2}/Views/%1/{1}/{0}.cshtml",
                    "~/Areas/{2}/Views/%1/{1}/{0}.vbhtml",
                    "~/Areas/{2}/Views/%1/Shared/{0}.cshtml",
                    "~/Areas/{2}/Views/%1/Shared/{0}.vbhtml"
                };
    
                AreaMasterLocationFormats = new[] {
                    "~/Areas/{2}/Views/%1/{1}/{0}.cshtml",
                    "~/Areas/{2}/Views/%1/{1}/{0}.vbhtml",
                    "~/Areas/{2}/Views/%1/Shared/{0}.cshtml",
                    "~/Areas/{2}/Views/%1/Shared/{0}.vbhtml"
                };
    
                AreaPartialViewLocationFormats = new[] {
                    "~/Areas/{2}/Views/%1/{1}/{0}.cshtml",
                    "~/Areas/{2}/Views/%1/{1}/{0}.vbhtml",
                    "~/Areas/{2}/Views/%1/Shared/{0}.cshtml",
                    "~/Areas/{2}/Views/%1/Shared/{0}.vbhtml"
                };
    
                ViewLocationFormats = new[] {
                    "~/Views/%1/{1}/{0}.cshtml",
                    "~/Views/%1/{1}/{0}.vbhtml",
                    "~/Views/%1/Shared/{0}.cshtml",
                    "~/Views/%1/Shared/{0}.vbhtml"
                };
    
                MasterLocationFormats = new[] {
                    "~/Views/%1/{1}/{0}.cshtml",
                    "~/Views/%1/{1}/{0}.vbhtml",
                    "~/Views/%1/Shared/{0}.cshtml",
                    "~/Views/%1/Shared/{0}.vbhtml"
                };
    
                PartialViewLocationFormats = new[] {
                    "~/Views/%1/{1}/{0}.cshtml",
                    "~/Views/%1/{1}/{0}.vbhtml",
                    "~/Views/%1/Shared/{0}.cshtml",
                    "~/Views/%1/Shared/{0}.vbhtml"
                };
            }
    
            protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
            {
                var nameSpace = controllerContext.Controller.GetType().Namespace;
                return base.CreatePartialView(controllerContext, partialPath.Replace("%1", nameSpace));
            }
    
            protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
            {
                var nameSpace = controllerContext.Controller.GetType().Namespace;
                return base.CreateView(controllerContext, viewPath.Replace("%1", nameSpace), masterPath.Replace("%1", nameSpace));
            }
    
            protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
            {
                var nameSpace = controllerContext.Controller.GetType().Namespace;
                return base.FileExists(controllerContext, virtualPath.Replace("%1", nameSpace));
            }
    
        }
    
    
    
        public class MyWebFormViewEngine : WebFormViewEngine
        {
            public MyWebFormViewEngine() : base()
            {
                MasterLocationFormats = new[] {
                    "~/Views/%1/{1}/{0}.master",
                    "~/Views/%1/Shared/{0}.master"
                };
    
                AreaMasterLocationFormats = new[] {
                    "~/Areas/{2}/Views/%1/{1}/{0}.master",
                    "~/Areas/{2}/Views/%1/Shared/{0}.master",
                };
    
                ViewLocationFormats = new[] {
                    "~/Views/%1/{1}/{0}.aspx",
                    "~/Views/%1/{1}/{0}.ascx",
                    "~/Views/%1/Shared/{0}.aspx",
                    "~/Views/%1/Shared/{0}.ascx"
                };
    
                AreaViewLocationFormats = new[] {
                    "~/Areas/{2}/Views/%1/{1}/{0}.aspx",
                    "~/Areas/{2}/Views/%1/{1}/{0}.ascx",
                    "~/Areas/{2}/Views/%1/Shared/{0}.aspx",
                    "~/Areas/{2}/Views/%1/Shared/{0}.ascx",
                };
    
                PartialViewLocationFormats = ViewLocationFormats;
                AreaPartialViewLocationFormats = AreaViewLocationFormats;
            }
    
            protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
            {
                var nameSpace = controllerContext.Controller.GetType().Namespace;
                return base.CreatePartialView(controllerContext, partialPath.Replace("%1", nameSpace));
            }
    
            protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
            {
                var nameSpace = controllerContext.Controller.GetType().Namespace;
                return base.CreateView(controllerContext, viewPath.Replace("%1", nameSpace), masterPath.Replace("%1", nameSpace));
            }
    
            protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
            {
                var nameSpace = controllerContext.Controller.GetType().Namespace;
                return base.FileExists(controllerContext, virtualPath.Replace("%1", nameSpace));
            }
    
        }
    

     

              Here, I am extending the RazorViewEngine and WebFormViewEngine class and then appending /%1 in each views location variable, so that we can replace /%1 at run-time. I am also overriding the FileExists, CreateView and CreatePartialView methods. In each of these method implementation, I am replacing /%1 with controller namespace. Now, just register these view engines in Application_Start method in Global.asax.cs file,

     

        protected void Application_Start()
        {
    	ViewEngines.Engines.Clear();
    	ViewEngines.Engines.Add(new MyRazorViewEngine());
    	ViewEngines.Engines.Add(new MyWebFormViewEngine());
    	................................................
    	................................................
        }
    

     

              Now just create a controller and put this controller's view inside Views/ControllerNameSpace/ControllerName folder and then run this application. You will find that everything works just fine.

     

        Summary:

              ASP.NET MVC uses convention over configuration to locate views. For many applications this convention to locate views is acceptable. But sometimes you may need to locate views at run-time. In this article, I showed you how you can dynamically locate your views by using a custom view engine. I am also attaching a sample application. Hopefully you will enjoy this article too.

     

    Read more...