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,
001 | public class MyRazorViewEngine : RazorViewEngine |
003 | public MyRazorViewEngine() : base () |
005 | AreaViewLocationFormats = new [] { |
006 | "~/Areas/{2}/Views/%1/{1}/{0}.cshtml" , |
007 | "~/Areas/{2}/Views/%1/{1}/{0}.vbhtml" , |
008 | "~/Areas/{2}/Views/%1/Shared/{0}.cshtml" , |
009 | "~/Areas/{2}/Views/%1/Shared/{0}.vbhtml" |
012 | AreaMasterLocationFormats = new [] { |
013 | "~/Areas/{2}/Views/%1/{1}/{0}.cshtml" , |
014 | "~/Areas/{2}/Views/%1/{1}/{0}.vbhtml" , |
015 | "~/Areas/{2}/Views/%1/Shared/{0}.cshtml" , |
016 | "~/Areas/{2}/Views/%1/Shared/{0}.vbhtml" |
019 | AreaPartialViewLocationFormats = new [] { |
020 | "~/Areas/{2}/Views/%1/{1}/{0}.cshtml" , |
021 | "~/Areas/{2}/Views/%1/{1}/{0}.vbhtml" , |
022 | "~/Areas/{2}/Views/%1/Shared/{0}.cshtml" , |
023 | "~/Areas/{2}/Views/%1/Shared/{0}.vbhtml" |
026 | ViewLocationFormats = new [] { |
027 | "~/Views/%1/{1}/{0}.cshtml" , |
028 | "~/Views/%1/{1}/{0}.vbhtml" , |
029 | "~/Views/%1/Shared/{0}.cshtml" , |
030 | "~/Views/%1/Shared/{0}.vbhtml" |
033 | MasterLocationFormats = new [] { |
034 | "~/Views/%1/{1}/{0}.cshtml" , |
035 | "~/Views/%1/{1}/{0}.vbhtml" , |
036 | "~/Views/%1/Shared/{0}.cshtml" , |
037 | "~/Views/%1/Shared/{0}.vbhtml" |
040 | PartialViewLocationFormats = new [] { |
041 | "~/Views/%1/{1}/{0}.cshtml" , |
042 | "~/Views/%1/{1}/{0}.vbhtml" , |
043 | "~/Views/%1/Shared/{0}.cshtml" , |
044 | "~/Views/%1/Shared/{0}.vbhtml" |
048 | protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) |
050 | var nameSpace = controllerContext.Controller.GetType().Namespace; |
051 | return base .CreatePartialView(controllerContext, partialPath.Replace( "%1" , nameSpace)); |
054 | protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) |
056 | var nameSpace = controllerContext.Controller.GetType().Namespace; |
057 | return base .CreateView(controllerContext, viewPath.Replace( "%1" , nameSpace), masterPath.Replace( "%1" , nameSpace)); |
060 | protected override bool FileExists(ControllerContext controllerContext, string virtualPath) |
062 | var nameSpace = controllerContext.Controller.GetType().Namespace; |
063 | return base .FileExists(controllerContext, virtualPath.Replace( "%1" , nameSpace)); |
070 | public class MyWebFormViewEngine : WebFormViewEngine |
072 | public MyWebFormViewEngine() : base () |
074 | MasterLocationFormats = new [] { |
075 | "~/Views/%1/{1}/{0}.master" , |
076 | "~/Views/%1/Shared/{0}.master" |
079 | AreaMasterLocationFormats = new [] { |
080 | "~/Areas/{2}/Views/%1/{1}/{0}.master" , |
081 | "~/Areas/{2}/Views/%1/Shared/{0}.master" , |
084 | ViewLocationFormats = new [] { |
085 | "~/Views/%1/{1}/{0}.aspx" , |
086 | "~/Views/%1/{1}/{0}.ascx" , |
087 | "~/Views/%1/Shared/{0}.aspx" , |
088 | "~/Views/%1/Shared/{0}.ascx" |
091 | AreaViewLocationFormats = new [] { |
092 | "~/Areas/{2}/Views/%1/{1}/{0}.aspx" , |
093 | "~/Areas/{2}/Views/%1/{1}/{0}.ascx" , |
094 | "~/Areas/{2}/Views/%1/Shared/{0}.aspx" , |
095 | "~/Areas/{2}/Views/%1/Shared/{0}.ascx" , |
098 | PartialViewLocationFormats = ViewLocationFormats; |
099 | AreaPartialViewLocationFormats = AreaViewLocationFormats; |
102 | protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) |
104 | var nameSpace = controllerContext.Controller.GetType().Namespace; |
105 | return base .CreatePartialView(controllerContext, partialPath.Replace( "%1" , nameSpace)); |
108 | protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) |
110 | var nameSpace = controllerContext.Controller.GetType().Namespace; |
111 | return base .CreateView(controllerContext, viewPath.Replace( "%1" , nameSpace), masterPath.Replace( "%1" , nameSpace)); |
114 | protected override bool FileExists(ControllerContext controllerContext, string virtualPath) |
116 | var nameSpace = controllerContext.Controller.GetType().Namespace; |
117 | 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,
1 | protected void Application_Start() |
3 | ViewEngines.Engines.Clear(); |
4 | ViewEngines.Engines.Add( new MyRazorViewEngine()); |
5 | ViewEngines.Engines.Add( new MyWebFormViewEngine()); |
6 | ................................................ |
7 | ................................................ |
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.