📜 ⬆️ ⬇️

Preparing ASP.NET Core: let's talk about non-standard approaches when working with views

We continue our column on the topic ASP.NET Core with a publication from Dmitry Sikorsky ( DmitrySikorsky ) - the head of the company “Yubreynyans” from Ukraine. In his next article, Dmitry talks about the experience of non-standard work with views in ASP.NET Core. Previous articles from the column can always be read on the link #aspnetcolumn - Vladimir Yunev
Recently, I worked a lot on my modular framework for ASP.NET 5 (now ASP.NET Core 1.0). Within the framework of this project, various tasks had to be solved, and one of them was working with views that are either just in non-standard places, or outside the main assembly of the web application. In this article I will try to tell you what options you have if you need something like that.


Views in non-standard places inside the main assembly of the web application


If, for some reason, your views are outside the Views folder that they put in (but still remain inside the main project of the application), you will need to inform Razor about this. If earlier for this, you would have to write a class derived from RazorViewEngine, now it is a little easier to do.

aspnetcolumngithub Tip! You can try everything yourself or by downloading the source code from GitHub https://github.com/DmitrySikorsky/AspNet5Views .
First, we implement the IViewLocationExpander interface (recall that “{1}” in the string constant will be replaced with the name of the controller, and “{0}” with the name of the action):

public class CustomViewLocationExpander : IViewLocationExpander { public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) { List<string> expandedViewLocations = new List<string>(); expandedViewLocations.AddRange(viewLocations); expandedViewLocations.Add("/Views/SomeExtraFolder/{1}/{0}.cshtml"); return expandedViewLocations; } public void PopulateValues(ViewLocationExpanderContext context) { } } 

Then we “register” an instance of the resulting class in the ConfigureServices method:
')
 services.Configure<RazorViewEngineOptions>(options => { options.ViewLocationExpanders.Add(new CustomViewLocationExpander()); } ); 

That's it, now Razor will take note of our new arrangement of views.

Resource views in other assemblies


Actually, I do not know about the advantages (but I know about the disadvantages!) Of this approach over the next one, but I still think it necessary to tell about it.

In order for views to be placed in an assembly as resources, you need to add the following line to the project.json of the corresponding project (in fact, it is not only views that become resources):

"Resource": "Views / **"

In order for such views to be later discovered by Razor, you need to implement the IFileProvider interface and assign an instance of the resulting class to the corresponding property in the ConfigureServices method in the main project:

 services.Configure<RazorViewEngineOptions>(options => { options.FileProvider = this.GetFileProvider(this.applicationBasePath); } ); 

The GetFileProvider method creates an instance of our CompositeFileProvider class, which, in essence, simply combines several different providers (in our case, these are physical files in the main project folder and resources from the specified assemblies).

The main disadvantage of this approach is that we cannot use types defined in assemblies that are not explicitly referenced in the main project for typing representations. For example, you cannot use types that are defined in the project in which the views themselves are located. This is due to the fact that representations in the form of resources are compiled at runtime and, accordingly, the types of their models cannot be found. This is not a problem if you can add dependencies explicitly, but if you need to load assemblies dynamically, at runtime, you cannot do this. The following approach solves this problem.

(Actually, as far as I understand, it can still be resolved. I discussed this issue here: https://github.com/aspnet/Mvc/issues/3413 , but the matter has not gone beyond this discussion yet.)

Precompiled Views in Other Assemblies


Perhaps, if you need to place the views in different assemblies of your application (for example, to divide it into independent parts and simplify maintenance and teamwork), then this is the best option.

As in the previous case, when using precompiled views, you do not need to copy them to the server when publishing, which significantly speeds up this process (instead of multiple cshtml files, only one dll file is copied). But, in contrast to the representations in the form of resources, such representations, as the name implies, are compiled at the project assembly stage, and not during execution, which, firstly, makes it possible to identify in advance all errors in them, and secondly, noticeably saves time when launching the application when accessing specific pages.

To force the compiler to compile the views, it is enough to create a RazorPreCompilation class in the corresponding project, inherit it from RazorPreCompileModule, override the EnablePreCompilation method in it so that it returns true, and finally put it in the \ Compiler \ PreProcess folder:

 public class RazorPreCompilation : RazorPreCompileModule { protected override bool EnablePreCompilation(BeforeCompileContext context) => true; } 

In the main project of the application, in order to inform Razor that he must use precompiled views from other assemblies, you simply need to pass a set of these assemblies to the appropriate method:

 services.AddMvc() .AddPrecompiledRazorViews( new Assembly[] { Assembly.Load(new AssemblyName("AspNet5Views.PrecompiledViews")) } ); 

This is enough to make it work.

As I wrote above, due to the fact that views are compiled at the assembly stage of the entire project, we have the opportunity to type them by types that are not referenced in the main project of the application.

findings


In general, everything is quite simple. The last two approaches have a significant drawback, namely, the need to restart the entire application to change, for example, even the smallest details in the layout. But, on the other hand, it is still much more convenient than copying views from all parts of the application into the main project, if you need to split the application into parts.

I prepared a small test project so that you can try everything that is described in this article: https://github.com/DmitrySikorsky/AspNet5Views .

To authors


Friends, if you are interested in supporting the column with your own material, please write to me at vyunev@microsoft.com to discuss all the details. We are looking for authors who can interestingly tell about ASP.NET and other topics.

about the author


Sikorsky Dmitry Alexandrovich
Jubreynians Company (http://ubrainians.com/)
Owner, Head
DmitrySikorsky

Source: https://habr.com/ru/post/276061/


All Articles