The great thing about ASP.NET has generally been its “get stuff done” attitude, with many defaults and conventions set for us right out of the box. Sane defaults mean we can be productive and build apps quickly without requiring a deep understanding of the inner-workings of ASP.NET as a development framework. The idea is excellent when everything is going well, but it can leave us frustrated when the actual results of code execution don’t match our expected results.
In this post, we’ll see how we can introspect the RazorViewEngine
to see where it searches for views, helping us understand when we don’t get the results we expected.
Getting Started
This approach will work with any ASP.NET project that is using ASP.NET Core or Razor pages and is utilizing the RazorViewEngine
as its main rendering view engine. If our project is using another view engine, then this approach will need to be modified.
To make sure we are using the RazorViewEngine
, we need to ensure we have registered either MVC or RazorPages in our IServiceCollection
.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
// or
services.AddMvc();
}
Note: The call to AddMvc
also calls AddRazorPages
internally, so we don’t need both registration calls inside our ConfigureServices
method.
RazorViewEngineOptions
In the previous section, we registered many of the mechanical parts of the RazorViewEngine
system. We won’t go into too much detail about all the things that were registered, but an essential type registered with our dependency injection was RazorViewEngineOptions
.
RazorViewEngineOptions
tells the RazorViewEngine
where it should look for our views. Properties include ViewLocationExpanders
, ViewLocationFormats
, AreaViewLocationFormats
, PageViewLocationFormats
, and AreaPageViewLocationFormats
.
Razor Page
We can build a Razor page to take advantage of RazorViewEngineOptions
and list out the locations for each property. Note, we need to inject
an instance of IOptions<RazorViewEngineOptions>
, or else we’ll get an exception.
@page
@using Microsoft.Extensions.Options
@using Microsoft.AspNetCore.Mvc.Razor
@model IndexModel
@inject IOptions<RazorViewEngineOptions> Options
@{
ViewData["Title"] = "Home page";
}
<h2>View Location Formats</h2>
<ul>
@{
var views =
Options.Value.ViewLocationFormats;
}
@foreach (var location in views)
{
<li>@location</li>
}
</ul>
<h2>Area View Location Formats</h2>
<ul>
@{
var areas =
Options.Value.AreaViewLocationFormats;
}
@foreach (var location in areas)
{
<li>@location</li>
}
</ul>
<h2>Page View Location Formats</h2>
<ul>
@{
var pages =
Options.Value.PageViewLocationFormats;
}
@foreach (var location in pages)
{
<li>@location</li>
}
</ul>
<h2>Area Page View Location Formats</h2>
<ul>
@{
var areaPages =
Options.Value.AreaPageViewLocationFormats;
}
@foreach (var location in areaPages)
{
<li>@location</li>
}
</ul>
When we run our ASP.NET application, we’ll see the following results on our rendered page.
View Location Formats
- /Views/{1}/{0}.cshtml
- /Views/Shared/{0}.cshtml
- /Pages/Shared/{0}.cshtml
Area View Location Formats
- /Areas/{2}/Views/{1}/{0}.cshtml
- /Areas/{2}/Views/Shared/{0}.cshtml
- /Views/Shared/{0}.cshtml
- /Pages/Shared/{0}.cshtml
Page View Location Formats
- /Pages/{1}/{0}.cshtml
- /Pages/Shared/{0}.cshtml
- /Views/Shared/{0}.cshtml
Area Page View Location Formats
- /Areas/{2}/Pages/{1}/{0}.cshtml
- /Areas/{2}/Pages/Shared/{0}.cshtml
- /Areas/{2}/Views/Shared/{0}.cshtml
- /Pages/Shared/{0}.cshtml
- /Views/Shared/{0}.cshtml
For MVC locations, the {0}
is a placeholder for the action name, where the {1}
is a placeholder for the controller name, and finally any reference to {2}
denotes the area’s name.
For Razor Pages locations, the {0}
placeholder denotes a view’s name, where the {1}
denotes a page’s name, and {2}
is again the area’s name.
We can alter these locations at registration time by adding additional locations that include the placeholders required to dynamically resolve views.
Conclusion
There we have it! We can use RazorViewEngineOptions
to understand where the RazorViewEngine
will look for our views. We can also augment the options at registration with more locations, and this technique works great for those with a customized setup. Hope you found this post helpful, and please leave a comment below.