ASP.NET Core Razor生成Html静态文件
程序员文章站
2022-05-13 21:37:35
一、前言 最近做项目的时候,使用 "Util" 进行开发,使用 写前端页面。初次使用感觉还是不大习惯,之前都是前后端分离的方式开发的,但是使用 "Util" 封装后的 后,感觉开发效率还是杠杠滴。 二、问题 在发布代码的时候, 打包异常,提示是缺少了某些 文件,我看了下相应的目录,发现目录缺少了部分 ......
一、前言
最近做项目的时候,使用进行开发,使用Razor
写前端页面。初次使用感觉还是不大习惯,之前都是前后端分离的方式开发的,但是使用封装后的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配置
一次性生成方式,调用一次接口即可
五、源码地址
Razor生成静态Html文件: 或者
六、参考
获取所有已注册的路由:
上一篇: Go语言中TCP/IP网络编程的深入讲解
推荐阅读
-
详解ASP.NET MVC 利用Razor引擎生成静态页
-
ASP.NET Core Razor生成Html静态文件
-
Node.js+jade抓取博客所有文章生成静态html文件的实例
-
asp.net MVC设计模式中使用iTextSharp实现html字符串生成PDF文件
-
Asp.Net Core 中的静态文件
-
学习ASP.NET Core Razor 编程系列十四——文件上传功能(二)
-
在ASP.NET Core中,静态类如何读取配置文件
-
Asp.Net Core Linux环境下 找不到配置文件、静态文件的问题
-
asp.net core项目中如何使用html文件
-
Django实战:Django 3.0 +Redis 3.4 +Celery 4.4异步生成静态HTML文件(附源码)