一、前言

最近做项目的时候,使用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

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

ASP.NET Core Razor生成Html静态文件的更多相关文章

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

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

  2. 《ASP.NET Core 高性能系列》静态文件中间件

    一.概述 静态文件(如 HTML.CSS.图片和 JavaScript等文件)是 Web程序直接提供给客户端的直接加载的文件. 较比于程序动态交互的代码而言,其实原理都一样(走Http协议), ASP ...

  3. 学习ASP.NET Core Razor 编程系列十四——文件上传功能(二)

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

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

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

  5. 学习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 Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  7. 学习ASP.NET Core Razor 编程系列十九——分页

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

  8. 学习ASP.NET Core Razor 编程系列十七——分组

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

  9. C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理

    C#编译器优化那点事   使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...

随机推荐

  1. Eclipse安装ModelGoon控件(ModelGoon控件反向生成UML)

    Eclipse安装ModelGoon 1 下载ModelGoon到本地,放在eclipse的安装目录下 2 打开Eclipse,点击Help,选择Install new software 3 点击ad ...

  2. 【转载】CentOS中crontab定时计划任务的使用

    转载自:http://blog.csdn.net/testcs_dn/article/details/48780971 概述 利用“任务计划”,可以将任何脚本.程序或文档安排在某个最方便的时间运行.通 ...

  3. Django使用Signals监测model字段变化发送通知

    上一篇文章<运维效率之数据迁移自动化>中讲到了工单通知,本文将介绍工单通知实现过程中的一些小技巧.所有演示均基于Django2.0 阅读此篇文章你可以: 解锁一个python if的使用新 ...

  4. jQuery同步Ajax带来的UI线程阻塞问题及解决方法

    遇到了同步Ajax引起的UI线程阻塞问题,在此记录一下. 事情起因是这样的,因为页面上有多个相似的异步请求动作,本着提高代码可重用性的原则,我封装了一个名为getData的函数,它接收不同参数,只负责 ...

  5. Java队列——线程池创建的例子

    线程池为线程生命周期开销问题和资源不足问题提供了解决方案.通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上.其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟.这 ...

  6. HBase:分布式列式NoSQL数据库

    传统的ACID数据库,可扩展性上受到了巨大的挑战.而HBase这类系统,兼具可扩展性的同时,也提出了类SQL的接口. HBase架构组成 HBase采用Master/Slave架构搭建集群,它隶属于H ...

  7. 【转】Java中堆和栈的区别

    Java的堆是一个运行时数据区,类的对象从中分配空间.这些对象通过new.newarray.anewarray和multianewarray等 指令建立,它们不需要程序代码来显式的释放.堆是由垃圾回收 ...

  8. 你的网站升级https了吗

    升级 HTTPS,价值何在? HTTPS 实质上是一种面向安全信息通信的协议.从最终的数据解析的角度上看,HTTPS 与 HTTP 没有本质上的区别.对于接收端而言,SSL/TSL 将接收的数据包解密 ...

  9. 【译】如何更好的使用javascript数组

    赶紧阅读读此文,我保证,在过去的几个月里我,我确定我在数组问题上犯过4次错误.于是我写下这篇文章,阅读这篇文章可以让你更准确的使用javascript数组的一些方法 使用Array.includes替 ...

  10. .3-浅析webpack源码之预编译总览

    写在前面: 本来一开始想沿用之前vue源码的标题:webpack源码之***,但是这个工具比较巨大,所以为防止有人觉得我装逼跑来喷我(或者随时鸽),加上浅析二字,以示怂. 既然是浅析,那么案例就不必太 ...