一, 前言
最近做项目的时候, 使用 https://github.com/dotnetcore/Util 进行开发, 使用 Razor 写前端页面. 初次使用感觉还是不大习惯, 之前都是前后端分离的方式开发的, 但是使用 https://github.com/dotnetcore/Util 封装后的 Angular 后, 感觉开发效率还是杠杠滴.
二, 问题
在发布代码的时候, webpack 打包异常, 提示是缺少了某些 html 文件, 我看了下相应的目录, 发现目录缺少了部分 Html 文件, 然后就问了何镇汐大大, 给出的解决方案是, 每个页面都需要访问一下才能生成相应的 Html 静态文件. 这时候就产生了疑虑, 是否有一种方式能获取所有路由, 然后只需访问一次即可生成所有的 Html 页面.
三, 解决方案
3.1 每次访问生成 Html
解决方案思路:
继承
ActionFilterAttribute
特性, 重写执行方法
访问的时候判断访问的 Result 是否 ViewResult, 如果是方可生成 Html
从 RazorViewEngine 中查找到 View 后进行渲染
- /// <summary>
- /// 生成 Html 静态文件
- /// </summary>
- public class HtmlAttribute : ActionFilterAttribute {
- /// <summary>
- /// 生成路径, 相对根路径, 范例:/Typings/app/app.component.html
- /// </summary>
- public string Path { get; set; }
- /// <summary>
- /// 路径模板, 范例: Typings/app/{area}/{controller}/{controller}-{action}.component.html
- /// </summary>
- public string Template { get; set; }
- /// <summary>
- /// 执行生成
- /// </summary>
- public override async Task OnResultExecutionAsync( ResultExecutingContext context, ResultExecutionDelegate next ) {
- await WriteViewToFileAsync( context );
- await base.OnResultExecutionAsync( context, next );
- }
- /// <summary>
- /// 将视图写入 html 文件
- /// </summary>
- private async Task WriteViewToFileAsync( ResultExecutingContext context ) {
- try {
- var html = await RenderToStringAsync( context );
- if( string.IsNullOrWhiteSpace( html ) )
- return;
- var path = Util.Helpers.Common.GetPhysicalPath( string.IsNullOrWhiteSpace( Path ) ? GetPath( context ) : Path );
- var directory = System.IO.Path.GetDirectoryName( path );
- if( string.IsNullOrWhiteSpace( directory ) )
- return;
- if( Directory.Exists( directory ) == false )
- Directory.CreateDirectory( directory );
- File.WriteAllText( path, html );
- }
- catch( Exception ex ) {
- ex.Log( Log.GetLog().Caption( "生成 html 静态文件失败" ) );
- }
- }
- /// <summary>
- /// 渲染视图
- /// </summary>
- protected async Task<string> RenderToStringAsync( ResultExecutingContext context ) {
- string viewName = "";
- object model = null;
- if( context.Result is ViewResult result ) {
- viewName = result.ViewName;
- viewName = string.IsNullOrWhiteSpace( viewName ) ? context.RouteData.Values["action"].SafeString() : viewName;
- model = result.Model;
- }
- var razorViewEngine = Ioc.Create<IRazorViewEngine>();
- var tempDataProvider = Ioc.Create<ITempDataProvider>();
- var serviceProvider = Ioc.Create<IServiceProvider>();
- var httpContext = new DefaultHttpContext { RequestServices = serviceProvider };
- var actionContext = new ActionContext( httpContext, context.RouteData, new ActionDescriptor() );
- using( var stringWriter = new StringWriter() ) {
- var viewResult = razorViewEngine.FindView( actionContext, viewName, true );
- if( viewResult.View == null )
- throw new ArgumentNullException( $"未找到视图: {viewName}" );
- var viewDictionary = new ViewDataDictionary( new EmptyModelMetadataProvider(), new ModelStateDictionary() ) { Model = model };
- var viewContext = new ViewContext( actionContext, viewResult.View, viewDictionary, new TempDataDictionary( actionContext.HttpContext, tempDataProvider ), stringWriter, new HtmlHelperOptions() );
- await viewResult.View.RenderAsync( viewContext );
- return stringWriter.ToString();
- }
- }
- /// <summary>
- /// 获取 Html 默认生成路径
- /// </summary>
- protected virtual string GetPath( ResultExecutingContext context ) {
- var area = context.RouteData.Values["area"].SafeString();
- var controller = context.RouteData.Values["controller"].SafeString();
- var action = context.RouteData.Values["action"].SafeString();
- var path = Template.Replace( "{area}", area ).Replace( "{controller}", controller ).Replace( "{action}", action );
- return path.ToLower();
- }
- }
3.2 一次访问生成所有 Html
解决方案思路:
获取所有已注册的路由
获取使用 RazorHtml 自定义特性的路由
忽略 Api 接口的路由
构建 RouteData 信息, 用于在 RazorViewEngine 中查找到相应的视图
构建 ViewContext 用于渲染出 Html 字符串
将渲染得到的 Html 字符串写入文件
获取所有注册的路由, 此处是比较重要的, 其他地方也可以用到.
- /// <summary>
- /// 获取所有路由信息
- /// </summary>
- /// <returns></returns>
- public IEnumerable<RouteInformation> GetAllRouteInformations()
- {
- List<RouteInformation> list = new List<RouteInformation>();
- var actionDescriptors = this._actionDescriptorCollectionProvider.ActionDescriptors.Items;
- foreach (var actionDescriptor in actionDescriptors)
- {
- RouteInformation info = new RouteInformation();
- if (actionDescriptor.RouteValues.ContainsKey("area"))
- {
- info.AreaName = actionDescriptor.RouteValues["area"];
- }
- // Razor 页面路径以及调用
- if (actionDescriptor is PageActionDescriptor pageActionDescriptor)
- {
- info.Path = pageActionDescriptor.ViewEnginePath;
- info.Invocation = pageActionDescriptor.RelativePath;
- }
- // 路由属性路径
- if (actionDescriptor.AttributeRouteInfo != null)
- {
- info.Path = $"/{actionDescriptor.AttributeRouteInfo.Template}";
- }
- // Controller/Action 的路径以及调用
- if (actionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
- {
- if (info.Path.IsEmpty())
- {
- info.Path =
- $"/{controllerActionDescriptor.ControllerName}/{controllerActionDescriptor.ActionName}";
- }
- var controllerHtmlAttribute = controllerActionDescriptor.ControllerTypeInfo.GetCustomAttribute<RazorHtmlAttribute>();
- if (controllerHtmlAttribute != null)
- {
- info.FilePath = controllerHtmlAttribute.Path;
- info.TemplatePath = controllerHtmlAttribute.Template;
- }
- var htmlAttribute = controllerActionDescriptor.MethodInfo.GetCustomAttribute<RazorHtmlAttribute>();
- if (htmlAttribute != null)
- {
- info.FilePath = htmlAttribute.Path;
- info.TemplatePath = htmlAttribute.Template;
- }
- info.ControllerName = controllerActionDescriptor.ControllerName;
- info.ActionName = controllerActionDescriptor.ActionName;
- info.Invocation = $"{controllerActionDescriptor.ControllerName}Controller.{controllerActionDescriptor.ActionName}";
- }
- info.Invocation += $"({actionDescriptor.DisplayName})";
- list.Add(info);
- }
- return list;
- }
生成 Html 静态文件
- /// <summary>
- /// 生成 Html 文件
- /// </summary>
- /// <returns></returns>
- public async Task Generate()
- {
- foreach (var routeInformation in _routeAnalyzer.GetAllRouteInformations())
- {
- // 跳过 API 的处理
- if (routeInformation.Path.StartsWith("/api"))
- {
- continue;
- }
- await WriteViewToFileAsync(routeInformation);
- }
- }
- /// <summary>
- /// 渲染视图为字符串
- /// </summary>
- /// <param name="info"> 路由信息 </param>
- /// <returns></returns>
- public async Task<string> RenderToStringAsync(RouteInformation info)
- {
- var razorViewEngine = Ioc.Create<IRazorViewEngine>();
- var tempDataProvider = Ioc.Create<ITempDataProvider>();
- var serviceProvider = Ioc.Create<IServiceProvider>();
- var routeData = new RouteData();
- if (!info.AreaName.IsEmpty())
- {
- routeData.Values.Add("area", info.AreaName);
- }
- if (!info.ControllerName.IsEmpty())
- {
- routeData.Values.Add("controller", info.ControllerName);
- }
- if (!info.ActionName.IsEmpty())
- {
- routeData.Values.Add("action", info.ActionName);
- }
- var httpContext = new DefaultHttpContext { RequestServices = serviceProvider };
- var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
- var viewResult = razorViewEngine.FindView(actionContext, info.ActionName, true);
- if (!viewResult.Success)
- {
- throw new InvalidOperationException($"找不到视图模板 {info.ActionName}");
- }
- using (var stringWriter = new StringWriter())
- {
- var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
- var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary, new TempDataDictionary(actionContext.HttpContext, tempDataProvider), stringWriter, new HtmlHelperOptions());
- await viewResult.View.RenderAsync(viewContext);
- return stringWriter.ToString();
- }
- }
- /// <summary>
- /// 将视图写入文件
- /// </summary>
- /// <param name="info"> 路由信息 </param>
- /// <returns></returns>
- public async Task WriteViewToFileAsync(RouteInformation info)
- {
- try
- {
- var html = await RenderToStringAsync(info);
- if (string.IsNullOrWhiteSpace(html))
- return;
- var path = Utils.Helpers.Common.GetPhysicalPath(string.IsNullOrWhiteSpace(info.FilePath) ? GetPath(info) : info.FilePath);
- var directory = System.IO.Path.GetDirectoryName(path);
- if (string.IsNullOrWhiteSpace(directory))
- return;
- if (Directory.Exists(directory) == false)
- Directory.CreateDirectory(directory);
- File.WriteAllText(path, html);
- }
- catch (Exception ex)
- {
- ex.Log(Log.GetLog().Caption("生成 html 静态文件失败"));
- }
- }
- protected virtual string GetPath(RouteInformation info)
- {
- var area = info.AreaName.SafeString();
- var controller = info.ControllerName.SafeString();
- var action = info.ActionName.SafeString();
- var path = info.TemplatePath.Replace("{area}", area).Replace("{controller}", controller).Replace("{action}", action);
- return path.ToLower();
- }
四, 使用方式
MVC 控制器配置
Startup 配置
一次性生成方式, 调用一次接口即可
五, 源码地址
- https://github.com/dotnetcore/Util
- https://github.com/bing-framework/Bing.NetCore
Razor 生成静态 Html 文件: https://github.com/dotnetcore/Util/tree/master/src/Util.Webs/Razors 或者 https://github.com/bing-framework/Bing.NetCore/tree/master/src/Bing.Webs/Razors
六, 参考
获取所有已注册的路由: https://github.com/kobake/AspNetCore.RouteAnalyzer
来源: https://www.cnblogs.com/jianxuanbing/p/9183359.html