While recently working on a NopCommerce project I needed to override a few different views from a theme and the Nop core. I was also looking for a way to include new views without having them be compiled into the plugin DLL, meaning they are actual files on the file system that get served up in the usual MVC fashion.
I quickly came across this blog post. It provides an overview of different ways to include views in a plugin, so thank you to Woon Cherk Lam. The section labeled The Advanced Method – “Custom View Engine” is what we are going to expand on here. Basically it shows you how to use a custom view engine to serve up view files and avoid compiling them into the DLL. This is a great concept but it did not solve my specific problem. The code in Woon’s blog post doesn’t prioritize the plugin’s views in all situations – sometimes you will want to be greedy and make sure your plugin’s views always take priority. This is what we will look at here.
Basically, the problem (in my situation) involved one line of code from Woon’s article:
The issue with this line of code is that the “Add” method appends the custom view engine to the end of the ViewEngines list, meaning it gets evaluated after the other view engines. ASP.NET comes with two view engines out of the box – razor and the legacy aspx engine. If either of these engines finds a view before your custom engine, those will be given priority. Since neither of these view engines search your plugin directories, they will never find your views first. Woon’s article shows how your custom view engine will have your plugin added as a search location, but if it is the last to search, some of your views will still never be found.
Side Note: Why doesn’t razor find views in your plugins? NopCommerce plugins are created as additional projects of the Class Library type. By default the razor view engine won’t search these types of new projects. Razor will only search projects added as MVC Areas.
The fix for this is simple – just change the line of code above to:
System.Web.Mvc.ViewEngines.Engines.Insert(0, new CustomViewEngine());
The insert method allows you to prioritize your custom view engine by explicitly adding it at the front of the list. This is of great significance, because it means all of NopCommerce will search your plugin’s views directories first every time it looks for a view.
This idea of a custom view engine that always gives your plugin priority when searching for views is a big deal in Nop. Woon does a good job explaining some of the benefits of this, but I want to expand and reiterate a few points on why this is valuable.
It allows you to override partial views without overriding the parent view.
This is a big deal. In MVC, partial views are fairly static razor files that get included into a parent view. Unlike Html.Action, Html.Partial does not hit a controller action in order to serve up a view. This creates a problem in Nop because there is no controller method or route to override. In order to override a partial view in Nop, you must first override the entire parent, and then change the call to the partial inside of that parent. For example, if you want to override the download button view that exists on a product details page, you have to override the entire product template that references it. However, with this custom view engine you need only drop your new version of the download button into the views directory of your plugin and you’re good to go.
It allows you to avoid overriding routes and mimicking folder structures.
With the custom view engine, you don’t need to override a controller method or route. View Engines do their work after a controller method is executed. If you just want to change which view gets served up in the response and not the logic behind it, you can simply create a new view with the same name and drop it into your plugin.
It allows you to output views as files on the file system rather than compiling them into the DLL.
Woon covers this in his post, but I want to reiterate what a great bonus this is, especially if you are doing product development and want to allow customers to edit the HTML and CSS for a view themselves. It also allows you to release multiple versions of the same view, such as different versions for different themes.
It allows you to not have to use fully qualified view names (no namespaces required).
This one is fairly self explanatory if you’ve read Woon’s article. You can once again refer to all views in your plugins by their actual name, meaning your controller action methods don’t need to specify fully qualified namespaces, etc.
I have not run into any issues using the custom view engine, as long as you follow the steps Woon provides. In fact, I find this solution more stable because you don’t need to interfere with routing and controller actions as often, which results in cleaner and lighter code. Remember to include the web.config in your views folder! This whole process makes working with views in Nop a lot easier. It is not uncommon to create a custom view engine in MVC for very specific tasks, so I don’t consider this a hack, especially since all logic and code is contained within the plugin.
P.S. Thanks again Woon!