ASP.Net MVC – Custom View Engines (Using the razor view engine as the base)

Okay, it’s ASP.Net MVC 3 time again! In this post I am going to discuss about custom view engines. I do not have a lot of real-time experience with custom view engines, but I wanted to try out one of my ideas of organizing the folder in a way that I feel was much more clearer. This is what I am talking about:

Views/
...Home/
......Pages/
.........Page1.cshtml
.........Page2.cshtml
......PartialPages/
........._UserControl1.cshtml
........._UserControl2.cshtml

From the textual folder structure above, I guess my intention is clear. In case you are familiar with the folder structure of MVC, There is a single “Views” folder and within this folder there are individual folders for every controller. Finally, the user controls and pages go in to these folders. If a page/user control is being shared it goes in to the “Shared” folder. In simple words, I just wanted to segregate the cshtml files further by creating a “Pages” and “PartialPages” folder.

As the name suggests, pages would go in to the “Pages” folder and user controls would go in to the “PartialPages” folder. To implement this I have created a custom view engine by inheriting the RazorViewEngine class. As the location of every file would change, we have to specify this by overriding the base string arrays that specify the possible locations of a certain type of a file (master, page, partial page).

Given below is the complete class. “%1″ in every possible path would have to be replaced with either “Pages” or “PartialPages” depending upon the method in question.

public class CustomRazorViewEngine : RazorViewEngine
{
	public CustomRazorViewEngine() : base()
	{
	    AreaViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/%1/{0}.cshtml",
						     "~/Areas/{2}/Views/{1}/%1/{0}.vbhtml",
						     "~/Areas/{2}/Views/Shared/%1/{0}.cshtml",
						     "~/Areas/{2}/Views/Shared/%1/{0}.vbhtml" };
	    AreaMasterLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/%1/{0}.cshtml",
						       "~/Areas/{2}/Views/{1}/%1/{0}.vbhtml",
						       "~/Areas/{2}/Views/Shared/%1/{0}.cshtml",
						       "~/Areas/{2}/Views/Shared/%1/{0}.vbhtml" };
	    AreaPartialViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/%1/{0}.cshtml",
							    "~/Areas/{2}/Views/{1}/%1/{0}.vbhtml",
							    "~/Areas/{2}/Views/Shared/%1/{0}.cshtml",
							    "~/Areas/{2}/Views/Shared/%1/{0}.vbhtml" };
	    ViewLocationFormats = new string[] { "~/Views/{1}/%1/{0}.cshtml",
						 "~/Views/{1}/%1/{0}.vbhtml",
						 "~/Views/Shared/%1/{0}.cshtml",
						 "~/Views/Shared/%1/{0}.vbhtml" };
	    MasterLocationFormats = new string[] { "~/Views/{1}/%1/{0}.cshtml",
						   "~/Views/{1}/%1/{0}.vbhtml",
						   "~/Views/Shared/%1/{0}.cshtml",
						   "~/Views/Shared/%1/{0}.vbhtml" };
	    PartialViewLocationFormats = new string[] { "~/Views/{1}/%1/{0}.cshtml",
							"~/Views/{1}/%1/{0}.vbhtml",
							"~/Views/Shared/%1/{0}.cshtml",
							"~/Views/Shared/%1/{0}.vbhtml" };
	    FileExtensions = new string[] { "cshtml", "vbhtml" };
	}

	protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
	{
	    return base.CreateView(controllerContext, viewPath.Replace("%1","Pages"), masterPath.Replace("%1","Pages"));
	}

	protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
	{
	    return base.CreatePartialView(controllerContext, partialPath.Replace("%1","PartialPages"));
	}

	protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
	{
	    return (base.FileExists(controllerContext, virtualPath.Replace("%1", "Pages")) ||
		    base.FileExists(controllerContext, virtualPath.Replace("%1", "PartialPages")));
	}
}

To download the file, click here (right click – save as – remove the .txt extension).

As you see above, to implement your custom view engine, you just have to override the following 3 methods:

  • CreateView – This method is called with the corresponding parameters if the view requested is a page
  • CreatePartialView – This method is called with the corresponding parameters if the view requested is a partial page / user control
  • FileExists – This method also has to be overridden in this case because the implementation of this method in the base class won’t know where to look for the file because of the %1 in the path. If this method is not overridden FileExists would always return false which would cause view engine to fail in the process of finding a matching view even if one exists

Now, update _ViewStart.cshtml to specify the location of the master page as shown below:

@{
    Layout = "~/Views/Shared/Pages/_Layout.cshtml";
}

Once we have the class that implements the custom razor engine, and the corresponding change to the _ViewStart.cshtml file, we have to inform the framework to use this instead of the default engine, as the default engine won’t work anymore as the folder structure has changed. To do this modify the Application_Start method in Global.asax.cs to include lines 5 & 6. Line 5 clears all the engines currently added (in this case, just the default engine) and line 6 adds the new custom engine we created.

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomRazorViewEngine());

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

Also, look forward to another post that dwelves in to internals of view engines in mvc 3!!!

Post your comments and happy coding!

Share