using System; using System.Collections.Generic; using System.Linq; using System.IO; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers;
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) { ControllerActionDescriptor cad = context.ActionContext.ActionDescriptor as ControllerActionDescriptor; string controllerNamespace = cad.ControllerTypeInfo.Namespace; int firstDotIndex = controllerNamespace.IndexOf('.'); int lastDotIndex = controllerNamespace.LastIndexOf('.'); if (firstDotIndex < 0) return viewLocations;
string viewLocation; if (firstDotIndex == lastDotIndex) { // controller folder is the first level sub folder of root folder viewLocation = "/{1}/Views/{0}.cshtml"; } else { string viewPath = controllerNamespace.Substring(firstDotIndex + 1, lastDotIndex - firstDotIndex - 1).Replace(".", "/"); viewLocation = $"/{viewPath}/{{1}}/Views/{{0}}.cshtml"; }
if (viewLocations.Any(l => l.Equals(viewLocation, StringComparison.InvariantCultureIgnoreCase))) return viewLocations;
if (viewLocations is List<string> locations) { locations.Add(viewLocation); return locations; }
// it turns out the viewLocations from ASP.NET Core is List<string>, so the code path should not go here. List<string> newViewLocations = viewLocations.ToList(); newViewLocations.Add(viewLocation); return newViewLocations; }
publicvoidConfigureServices(IServiceCollection services) { IMvcBuilder mvcBuilder = services.AddMvc(); mvcBuilder.AddRazorOptions(options => { // options.ViewLocationFormats.Add("/{1}/Views/{0}.cshtml"); we don't need this any more if we make use of NamespaceViewLocationExpander options.ViewLocationExpanders.Add(new NamespaceViewLocationExpander()); }); }
在这个 DI 为王的 ASP.NET Core 世界里,RazorTemplateEngine 也被注册为 DI 里的服务,因此我目前的做法继承 MvcRazorTemplateEngine 类,微调 GetImportItems 方法的逻辑,加入我们的特定路径,然后注册到 DI 取代原来的实现类型。代码如下:
internal ViewLocationCacheResult CreateCacheResult( HashSet<IChangeToken> expirationTokens, string relativePath, bool isMainPage) { var factoryResult = _pageFactory.CreateFactory(relativePath); var viewDescriptor = factoryResult.ViewDescriptor; if (viewDescriptor?.ExpirationTokens != null) { for (var i = 0; i < viewDescriptor.ExpirationTokens.Count; i++) { expirationTokens.Add(viewDescriptor.ExpirationTokens[i]); } }
if (factoryResult.Success) { // Only need to lookup _ViewStarts for the main page. var viewStartPages = isMainPage ? GetViewStartPages(viewDescriptor.RelativePath, expirationTokens) : Array.Empty<ViewLocationCacheItem>(); if (viewDescriptor.IsPrecompiled) { _logger.PrecompiledViewFound(relativePath); }
returnnew ViewLocationCacheResult( new ViewLocationCacheItem(factoryResult.RazorPageFactory, relativePath), viewStartPages); }
returnnull; }
private IReadOnlyList<ViewLocationCacheItem> GetViewStartPages( string path, HashSet<IChangeToken> expirationTokens) { var viewStartPages = new List<ViewLocationCacheItem>();
foreach (var viewStartProjectItem in _razorProject.FindHierarchicalItems(path, ViewStartFileName)) { var result = _pageFactory.CreateFactory(viewStartProjectItem.FilePath); var viewDescriptor = result.ViewDescriptor; if (viewDescriptor?.ExpirationTokens != null) { for (var i = 0; i < viewDescriptor.ExpirationTokens.Count; i++) { expirationTokens.Add(viewDescriptor.ExpirationTokens[i]); } }
if (result.Success) { // Populate the viewStartPages list so that _ViewStarts appear in the order the need to be // executed (closest last, furthest first). This is the reverse order in which // ViewHierarchyUtility.GetViewStartLocations returns _ViewStarts. viewStartPages.Insert(0, new ViewLocationCacheItem(result.RazorPageFactory, viewStartProjectItem.FilePath)); } }
// the items are in the order of closest first, furthest last, therefore we append our item to be the last item. return items.Append(GetItem("/Shared/Views/" + fileName)); } } }
完成之后再注册到 DI。
1 2 3 4 5 6 7 8 9 10 11 12 13
// Startup.cs
// using Microsoft.AspNetCore.Razor.Language;
publicvoidConfigureServices(IServiceCollection services) { // services.AddSingleton<RazorTemplateEngine, ModuleRazorTemplateEngine>(); // we don't need this any more if we make use of ModuleBasedRazorProject services.AddSingleton<RazorProject, ModuleBasedRazorProject>();