Great news... In ASP.NET Core 2 and up, you don't need a custom ViewEngine or even ExpandViewLocations anymore.
Using the OdeToCode.AddFeatureFolders Package
This is the easiest way... K. Scott Allen has a nuget package for you at OdeToCode.AddFeatureFolders that is clean and includes optional support for areas. Github: https://github.com/OdeToCode/AddFeatureFolders
Install the package, and it's as simple as:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddFeatureFolders();
...
}
...
}
DIY
Use this if you need extremely fine control over your folder structure, or if you aren't allowed/don't want to take the dependency for whatever reason. This is also quite easy, although perhaps more cluttery than the nuget package above:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<RazorViewEngineOptions>(o =>
{
// {2} is area, {1} is controller,{0} is the action
o.ViewLocationFormats.Clear();
o.ViewLocationFormats.Add("/Controllers/{1}/Views/{0}" + RazorViewEngine.ViewExtension);
o.ViewLocationFormats.Add("/Controllers/Shared/Views/{0}" + RazorViewEngine.ViewExtension);
// Untested. You could remove this if you don't care about areas.
o.AreaViewLocationFormats.Clear();
o.AreaViewLocationFormats.Add("/Areas/{2}/Controllers/{1}/Views/{0}" + RazorViewEngine.ViewExtension);
o.AreaViewLocationFormats.Add("/Areas/{2}/Controllers/Shared/Views/{0}" + RazorViewEngine.ViewExtension);
o.AreaViewLocationFormats.Add("/Areas/Shared/Views/{0}" + RazorViewEngine.ViewExtension);
});
...
}
...
}
And that's it! No special classes required.
Dealing with Resharper/Rider
Bonus tip: if you're using ReSharper, you might notice that in some places ReSharper can't find your views and gives you annoying warnings. To work around that, pull in the Resharper.Annotations package and in your startup.cs (or anywhere else really) add one of these attributes for each of your view locations:
[assembly: AspMvcViewLocationFormat("/Controllers/{1}/Views/{0}.cshtml")]
[assembly: AspMvcViewLocationFormat("/Controllers/Shared/Views/{0}.cshtml")]
[assembly: AspMvcViewLocationFormat("/Areas/{2}/Controllers/{1}/Views/{0}.cshtml")]
[assembly: AspMvcViewLocationFormat("/Controllers/Shared/Views/{0}.cshtml")]
Hopefully this spares some folks the hours of frustration I just lived through. :)
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…