一、前言

最近做项目的时候,使用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. Mac 安装 Ruby, Rails 运行环境

    对于新入门的开发者,如何安装 Ruby, Ruby Gems 和 Rails 的运行环境可能会是个问题,本页主要介绍如何用一条靠谱的路子快速安装 Ruby 开发环境. 次安装方法同样适用于产品环境! ...

  2. keepalived双机热备,安装部署文档

    keepalived双击热备,安装部署文档: 下载目录:/apps/keepalived-1.2.7.tar.gz 1:---> yum install -y make wget 2:---&g ...

  3. 有意思的App

    掘金 javadoop 专业相机也羡慕奖 – Focos 说个睡前故事 so easy 奖 – 洪恩双语绘本 效率蹭蹭上升奖 – Sorted³ 时光隧道走一回奖 – NOMO 相机 设计师也爱用奖 ...

  4. List集合中的对象按照某个字段去重实现

    package com.liying.banana.user; import java.util.ArrayList; import java.util.Comparator; import java ...

  5. MVC源码分析 - Authorize授权过滤器

    从 上一篇 其实能看到, 程序执行的过滤器, 有四种 : 过滤器类型 接口 描述 Authorization IAuthorizationFilter 此类型(或过滤器)用于限制进入控制器或控制器的某 ...

  6. JVM内存初学 堆、栈、方法区

    转自: http://www.open-open.com/lib/view/open1432200119489.html 这两天看了一下深入浅出JVM这本书,推荐给高级的java程序员去看,对你了解J ...

  7. ArcGIS紧凑型切片读取与应用3-紧凑型批量转分散型(附源码)

    1.前言 上篇介绍了webgis动态加载解析紧凑型切片的例子,现在我们使用逆向思维实现紧凑型切片转分散型切片,在实际工作中很有用处,紧凑型切片易于拷贝,但读取只有部署到Arcgis Server才行. ...

  8. JS存储cookie读取cookie删除cookie详细用法

    假设有这样一种情况,在某个用例流程中,由A页面跳至B页面,若在A页面中采用JS用变量temp保存了某一变量的值,在B页面的时候,同样需要使用JS来引用temp的变量值,对于JS中的全局变量或者静态变量 ...

  9. glob 在webpack中的使用。

    glob 在webpack中对文件的路径处理非常之方便,比如当搭建多页面应用时就可以使用glob对页面需要打包文件的路径进行很好的处理. 官方文档地址 : https://www.npmjs.com/ ...

  10. 【Java并发编程】3、DelayQueue应用场景,多考生考试

    该场景来自于:http://www.cnblogs.com/sunzhenchao/p/3515085.html. 模拟一个考试的日子,考试时间为120分钟,30分钟后才可交卷,当时间到了,或学生都交 ...