如何在ASP.NET Core MVC中用C#反射列出所有视图及其元素?
在ASP.NET Core MVC中列出所有视图及元素信息的实现方案
可以通过反射定位项目结构+HTML解析提取元素的组合方式实现需求,因为视图(.cshtml)并非编译后的CLR类型,无法直接通过反射获取元素信息,反射主要用来辅助识别Area、Controller对应的视图文件路径,再通过HTML解析器提取元素属性。
核心思路
- 利用反射获取项目中所有Controller类型,结合
AreaAttribute识别Area信息,构建视图文件的物理路径。 - 遍历所有.cshtml视图文件,使用HTML解析器(如AngleSharp)解析文件内容,提取目标元素(button、input等)的属性。
- 将提取的信息封装为统一模型,返回或存储。
实现步骤
1. 定义数据模型
用于存储提取的视图元素信息:
public class ViewElementInfo { public string Area { get; set; } public string Controller { get; set; } public string ViewName { get; set; } public string ElementType { get; set; } public string AspFor { get; set; } public string Id { get; set; } public string Name { get; set; } }
2. 扫描所有视图文件
通过反射获取Controller信息,定位项目中所有.cshtml文件的路径:
using System.Reflection; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Hosting; public class ViewScanner { private readonly IWebHostEnvironment _webHostEnv; private readonly IControllerTypeProvider _controllerTypeProvider; public ViewScanner(IWebHostEnvironment webHostEnv, IControllerTypeProvider controllerTypeProvider) { _webHostEnv = webHostEnv; _controllerTypeProvider = controllerTypeProvider; } public IEnumerable<string> GetAllViewPaths() { var viewPaths = new List<string>(); var rootPath = _webHostEnv.ContentRootPath; var controllerTypes = _controllerTypeProvider.ControllerTypes; foreach (var controllerType in controllerTypes) { // 提取Area名称 var areaAttr = controllerType.GetCustomAttribute<AreaAttribute>(); string areaName = areaAttr?.RouteValue; // 提取Controller名称(移除Controller后缀) string controllerName = controllerType.Name.Replace("Controller", "", StringComparison.OrdinalIgnoreCase); // 构建Controller对应的视图目录 string viewDirPath = string.IsNullOrEmpty(areaName) ? Path.Combine(rootPath, "Views", controllerName) : Path.Combine(rootPath, "Areas", areaName, "Views", controllerName); if (Directory.Exists(viewDirPath)) { viewPaths.AddRange(Directory.GetFiles(viewDirPath, "*.cshtml", SearchOption.AllDirectories)); } // 添加共享视图路径 string sharedViewDir = string.IsNullOrEmpty(areaName) ? Path.Combine(rootPath, "Views", "Shared") : Path.Combine(rootPath, "Areas", areaName, "Views", "Shared"); if (Directory.Exists(sharedViewDir)) { viewPaths.AddRange(Directory.GetFiles(sharedViewDir, "*.cshtml", SearchOption.AllDirectories)); } } return viewPaths.Distinct(); } }
3. 解析视图元素
使用AngleSharp解析视图内容,提取目标元素及属性(需先安装NuGet包:Install-Package AngleSharp):
using AngleSharp; using AngleSharp.Dom; using Microsoft.AspNetCore.Hosting; public class ViewElementParser { public async Task<List<ViewElementInfo>> ParseViewElements(string viewPath, IWebHostEnvironment webHostEnv) { var elements = new List<ViewElementInfo>(); var rootPath = webHostEnv.ContentRootPath; var relativePath = Path.GetRelativePath(rootPath, viewPath); var pathSegments = relativePath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); string area = null; string controller = null; string viewName = Path.GetFileNameWithoutExtension(viewPath); // 解析Area信息 if (pathSegments.Contains("Areas")) { int areaIndex = Array.IndexOf(pathSegments, "Areas"); area = pathSegments[areaIndex + 1]; int viewsIndex = Array.IndexOf(pathSegments, "Views"); if (viewsIndex != -1 && viewsIndex + 1 < pathSegments.Length && !pathSegments[viewsIndex + 1].Equals("Shared", StringComparison.OrdinalIgnoreCase)) { controller = pathSegments[viewsIndex + 1]; } } else { int viewsIndex = Array.IndexOf(pathSegments, "Views"); if (viewsIndex != -1 && viewsIndex + 1 < pathSegments.Length && !pathSegments[viewsIndex + 1].Equals("Shared", StringComparison.OrdinalIgnoreCase)) { controller = pathSegments[viewsIndex + 1]; } } // 读取并解析视图内容 var htmlContent = await File.ReadAllTextAsync(viewPath); var config = Configuration.Default; using var context = BrowsingContext.New(config); var document = await context.OpenAsync(req => req.Content(htmlContent)); // 提取button和input元素 var targetElements = document.QuerySelectorAll("button, input"); foreach (var element in targetElements) { elements.Add(new ViewElementInfo { Area = area, Controller = controller, ViewName = viewName, ElementType = element.TagName.ToLower(), AspFor = element.GetAttribute("asp-for"), Id = element.GetAttribute("id"), Name = element.GetAttribute("name") }); } return elements; } }
4. 注册服务与使用
在Program.cs中注册所需服务:
builder.Services.AddSingleton<IControllerTypeProvider, DefaultControllerTypeProvider>(); builder.Services.AddScoped<ViewScanner>(); builder.Services.AddScoped<ViewElementParser>();
创建API接口用于获取结果:
using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/views")] public class ViewElementsController : ControllerBase { private readonly ViewScanner _viewScanner; private readonly ViewElementParser _elementParser; private readonly IWebHostEnvironment _webHostEnv; public ViewElementsController(ViewScanner viewScanner, ViewElementParser elementParser, IWebHostEnvironment webHostEnv) { _viewScanner = viewScanner; _elementParser = elementParser; _webHostEnv = webHostEnv; } [HttpGet("elements")] public async Task<IActionResult> GetAllViewElements() { var viewPaths = _viewScanner.GetAllViewPaths(); var allElements = new List<ViewElementInfo>(); foreach (var path in viewPaths) { var elements = await _elementParser.ParseViewElements(path, _webHostEnv); allElements.AddRange(elements); } return Ok(allElements); } }
注意事项
- 反射的作用:仅用于获取Controller的Area和名称,从而定位视图文件,无法直接提取视图元素(因为视图是Razor模板,编译后是动态类,无法通过反射获取HTML结构)。
- AngleSharp兼容性:能正确解析Razor视图中的HTML元素,即使存在Razor代码(如
@model、@if),也不会影响元素属性的提取。 - 共享视图:共享视图的
Controller字段会为null,因为它们不属于特定Controller。 - 动态视图:运行时动态生成的视图(无物理.cshtml文件)无法被扫描到。
- 文件权限:确保应用程序拥有读取项目视图文件的权限。
内容的提问来源于stack exchange,提问作者ebrahim movahedinia
相关产品推荐
相关产品推荐

