如何重写RazorViewEngine使其递归查找EditorTemplate?
扩展RazorViewEngine实现EditorTemplates递归查找
这个需求其实挺常见的——默认的RazorViewEngine只会在Shared/EditorTemplates根目录下查找模板,不会遍历子文件夹。不过我们可以通过自定义ViewEngine子类,重写视图查找逻辑来实现递归查找的功能,具体步骤如下:
1. 创建自定义RazorViewEngine子类
我们需要重写FindPartialView方法(因为EditorTemplate本质上是PartialView的一种),在方法里递归遍历Shared/EditorTemplates的所有子文件夹,生成所有可能的模板路径并逐一检查:
using System.Collections.Generic; using System.IO; using System.Web.Mvc; public class RecursiveEditorTemplateRazorViewEngine : RazorViewEngine { protected override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) { // 判断当前是否在查找EditorTemplate(MVC内部会给模板名加上EditorTemplates/前缀) if (partialViewName.StartsWith("EditorTemplates/")) { var templateName = partialViewName.Substring("EditorTemplates/".Length); var editorTemplatesRoot = controllerContext.HttpContext.Server.MapPath("~/Views/Shared/EditorTemplates"); // 递归获取EditorTemplates下的所有子文件夹 var subFolders = GetAllSubFolders(editorTemplatesRoot); // 构建所有可能的模板查找路径 var viewLocations = new List<string>(); // 先添加默认的根目录路径(保持原有优先级) viewLocations.Add($"~/Views/Shared/EditorTemplates/{templateName}.cshtml"); viewLocations.Add($"~/Views/Shared/EditorTemplates/{templateName}.vbhtml"); // 添加所有子文件夹的路径 foreach (var folder in subFolders) { // 转换为MVC识别的虚拟路径格式 var relativePath = folder.Substring(editorTemplatesRoot.Length).Replace("\\", "/"); viewLocations.Add($"~/Views/Shared/EditorTemplates{relativePath}/{templateName}.cshtml"); viewLocations.Add($"~/Views/Shared/EditorTemplates{relativePath}/{templateName}.vbhtml"); } // 遍历路径,查找存在的模板 foreach (var location in viewLocations) { if (FileExists(controllerContext, location)) { var view = CreatePartialView(controllerContext, location); return new ViewEngineResult(view, this); } } // 如果所有路径都找不到,返回包含所有查找路径的结果(和基类行为一致) return new ViewEngineResult(viewLocations); } // 非EditorTemplate的查找请求,交给基类处理 return base.FindPartialView(controllerContext, partialViewName, useCache); } // 递归遍历获取所有子文件夹的私有方法 private List<string> GetAllSubFolders(string rootPath) { var folders = new List<string>(); try { foreach (var folder in Directory.GetDirectories(rootPath)) { folders.Add(folder); // 递归遍历子文件夹的子文件夹 folders.AddRange(GetAllSubFolders(folder)); } } catch (DirectoryNotFoundException) { // 如果EditorTemplates目录不存在,直接返回空列表,避免抛出异常 } return folders; } }
2. 注册自定义ViewEngine
在Global.asax.cs的Application_Start方法中,移除默认的RazorViewEngine,替换为我们自定义的实现:
protected void Application_Start() { // 移除默认的RazorViewEngine var defaultRazorEngine = ViewEngines.Engines.OfType<RazorViewEngine>().FirstOrDefault(); if (defaultRazorEngine != null) { ViewEngines.Engines.Remove(defaultRazorEngine); } // 添加自定义的递归查找ViewEngine ViewEngines.Engines.Add(new RecursiveEditorTemplateRazorViewEngine()); // 其他初始化代码... AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); }
关键逻辑说明
- 优先级保持:我们依然优先查找
Shared/EditorTemplates根目录下的模板,只有根目录找不到时才会去子文件夹查找,符合MVC原有的模板覆盖逻辑。 - 动态路径生成:通过递归遍历物理目录,动态生成所有子文件夹的虚拟路径,不需要硬编码任何子文件夹名称。
- 异常处理:捕获
DirectoryNotFoundException,避免因Shared/EditorTemplates目录不存在导致的程序崩溃。 - 兼容原有逻辑:非EditorTemplate的视图/部分视图查找请求,依然交给基类处理,不会影响项目中其他视图的正常工作。
内容的提问来源于stack exchange,提问作者Junior
相关产品推荐
相关产品推荐

