一、前言

最近做项目的时候,使用Util进行开发,使用Razor写前端页面。初次使用感觉还是不大习惯,之前都是前后端分离的方式开发的,但是使用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配置

  • 一次性生成方式,调用一次接口即可

五、源码地址

Util: https://github.com/dotnetcore/Util

Bing.NetCore: 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


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

ASP.NET Core Razor生成Html静态文件相关推荐

  1. 学习ASP.NET Core Razor 编程系列十三——文件上传功能(一)

    原文:学习ASP.NET Core Razor 编程系列十三--文件上传功能(一) 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习A ...

  2. 学习ASP.NET Core Razor 编程系列十八——并发解决方案

    原文:学习ASP.NET Core Razor 编程系列十八--并发解决方案 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP. ...

  3. netcore 编译 html dll,ASP.NET Core Razor 视图预编译、动态编译

    0x01 前言 ASP.NET Core在默认发布情况下,会启动预编译将试图编译成xx.Views.dll,也许在视图中打算修改一处很细小的地方我们需要再重新编译视图进行发布.下面我将从 ASP.NE ...

  4. 使用EntityFramework Core和Enums作为字符串的ASP.NET Core Razor页面——第一部分

    目录 介绍 背景 开发环境 使用代码 开始构建UI和数据访问 EF迁移和数据库创建 摘要 创建初始对象模型和数据库并显示第一个Razor页面 下载源码(QuantumWeb) 介绍 这是一篇由多部分组 ...

  5. 学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二--添加一个实体 学习ASP.NET ...

  6. ASP.NET Core Razor 标签助手 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Razor 标签助手 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Razor 标签助手 上一章节我们介绍了视图导入,学习了 ...

  7. ASP.NET Core - Razor页面之Handlers处理方法

    简介 在前一篇文章中,我们讨论了Razor页面.今天我们来谈谈处理方法(Handlers). 我们知道可以将代码和模型放在 .cshtml 文件里面或与 .cshtml 匹配的 .cshtml.cs ...

  8. ASP.Net Core Razor 部署AdminLTE框架

    1.AdminLTE 一个基于 bootstrap 的轻量级后台模板 2.AdminLTE 文档 在线中文Demo:http://adminlte.la998.com/ 在线中文文档:http://a ...

  9. 如何ASP.NET Core Razor中处理Ajax请求

    在ASP.NET Core Razor(以下简称Razor)刚出来的时候,看了一下官方的文档,一直没怎么用过.今天闲来无事,准备用Rozor做个项目熟练下,结果写第一个页面就卡住了..折腾半天才搞好, ...

最新文章

  1. C# 最快的逐一打印斐波那契结果数列的算法
  2. Python之list对应元素求和
  3. 21个WordPress店铺必备免费工具
  4. [jQuery原理] jQuery基本结构
  5. SpringBoot整合kafka(安装)
  6. 10行Python代码搞定目标检测
  7. 再见,Kafka!RocketMQ已成气候!
  8. html label 两端对齐,如果实现表单的label文字两端对齐
  9. ASP.NET中的注释 .
  10. 一文了解“最好编程语言”PHP 必知的 16 个编程法则!
  11. redis在linux中安装目录,小刀博客园
  12. bzoj 3165: [Heoi2013]Segment 线段树
  13. linux下SVN CVS命令大全
  14. WebLogic 11gR1修改jdk版本
  15. SpringCloud微服务架构前后端分离项目实践
  16. Eclipse主题与Monaco字体
  17. comsol通直流电_基于COMSOL仿真的高压直流交联聚乙烯(XLPE)电缆附件设计
  18. Mac OS 简易U盘重装系统 亲测
  19. 微信跳转浏览器提示_微信浏览器中打开提示的2种实现方式
  20. 笔记本电脑找不到个人热点的解决方法

热门文章

  1. 稍微成型点的用WEBSOCKET实现的实时日志LOG输出
  2. nat+端口转发,使得宿主机secureCRT可以访问vbox里linux虚拟机
  3. [转]软件测试演义——中高级系列(序)
  4. 基于电子邮件的InfoPath表单发布的注意点 [Infopath 2007]
  5. AspNet Core 6.0 Json写默认首字母小写(camelCase)问题
  6. Avalonia跨平台入门第二十三篇之滚动字幕
  7. 分库分表之历史表如何选择最佳分片路由规则
  8. 体验.NET Core使用IKVM对接Java
  9. 手把手教你学Dapr - 3. 使用Dapr运行第一个.Net程序
  10. C#获取摄像头拍照显示图像