NETCore RazorPages 静态生成方案

分类: NetCore

PageModel静态化方案查找过很多资料。
基本上没找到一个使用视图引擎静态化PageModel的方案。
最终也只是解决了分部视图静态化方式,对于Layout也是无能为力。

以下实现了PageModel扩展实现了对视图渲染转换成Html。此方法不包含Layout和Section。

  1. /// <summary>
  2. /// 转换成Html
  3. /// </summary>
  4. /// <param name="pageModel"></param>
  5. /// <param name="pageName"></param>
  6. /// <returns></returns>
  7. public async Task<string> ToHtml(this PageModel pageModel, string pageName)
  8. {
  9. var actionContext = new ActionContext(
  10. pageModel.HttpContext,
  11. pageModel.RouteData,
  12. pageModel.PageContext.ActionDescriptor
  13. );
  14. using (var sw = new StringWriter())
  15. {
  16. IRazorViewEngine _razorViewEngine = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorViewEngine)) as IRazorViewEngine;
  17. IRazorPageActivator _activator = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorPageActivator)) as IRazorPageActivator;
  18. var result = _razorViewEngine.FindPage(actionContext, pageName);
  19. if (result.Page == null)
  20. {
  21. throw new ArgumentNullException($"The page {pageName} cannot be found.");
  22. }
  23. var page = result.Page;
  24. var view = new RazorView(_razorViewEngine,
  25. _activator,
  26. new List<IRazorPage>(),
  27. page,
  28. HtmlEncoder.Default,
  29. new DiagnosticListener("ViewRenderService"));
  30. var viewContext = new ViewContext(
  31. actionContext,
  32. view,
  33. pageModel.ViewData,
  34. pageModel.TempData,
  35. sw,
  36. new HtmlHelperOptions()
  37. );
  38. var pageNormal = ((Page)result.Page);
  39. pageNormal.PageContext = pageModel.PageContext;
  40. pageNormal.ViewContext = viewContext;
  41. _activator.Activate(pageNormal, viewContext);
  42. await page.ExecuteAsync();
  43. return sw.ToString();
  44. }
  45. }

但是由于某些原因,我们需要生成包含layout的整个HTML页面,所以暂时没有别的办法,只能通过ActionFilter拦截的方式获取生成后的HTML,如下方代码

  1. /// <summary>
  2. /// Razor生成Html静态文件(保存目录为wwwroot)
  3. /// </summary>
  4. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
  5. public class HtmlStaticAttribute : ActionFilterAttribute
  6. {
  7. /// <summary>
  8. /// 路径模板,范例:static/{area}/{controller}/{action}.component.html
  9. /// </summary>
  10. public string Template { get; set; }
  11. /// <summary>
  12. /// 生成的最小间隔,单位(秒),比如设置5分钟,那么5分钟之内不会再生成
  13. /// </summary>
  14. public int MinInterval { get; set; } = 0;
  15. /// <summary>
  16. /// 结果执行之前 before
  17. /// </summary>
  18. /// <param name="context"></param>
  19. /// <param name="next"></param>
  20. /// <returns></returns>
  21. public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
  22. {
  23. if ((context.Result is PageResult || context.Result is ViewResult) && IsBuildHtml(RouteReplace(context, Template)))
  24. {
  25. var response = context.HttpContext.Response;
  26. if (!response.Body.CanRead || !response.Body.CanSeek)
  27. {
  28. using (var ms = new MemoryStream())
  29. {
  30. var old = response.Body;
  31. response.Body = ms;
  32. await base.OnResultExecutionAsync(context, next);
  33. if (response.StatusCode == 200)
  34. {
  35. await WriteHtml(context, response.Body);
  36. }
  37. ms.Position = 0;
  38. await ms.CopyToAsync(old);
  39. response.Body = old;
  40. }
  41. }
  42. else
  43. {
  44. await base.OnResultExecutionAsync(context, next);
  45. var old = response.Body.Position;
  46. if (response.StatusCode == 200)
  47. {
  48. await WriteHtml(context, response.Body);
  49. }
  50. response.Body.Position = old;
  51. }
  52. }
  53. else
  54. {
  55. await base.OnResultExecutionAsync(context, next);
  56. }
  57. }
  58. /// <summary>
  59. /// 根据路由参数进行模板替换
  60. /// </summary>
  61. /// <param name="context"></param>
  62. /// <param name="template">比如:static/{area}/{controller}/{action}/{id}.html</param>
  63. /// <returns></returns>
  64. public static string RouteReplace(ActionContext context, string template)
  65. {
  66. var path = template;
  67. foreach (var route in context.RouteData.Values)
  68. path = path.Replace("{" + route.Key + "}", route.Value.SafeString());
  69. return path.ToLower();
  70. }
  71. /// <summary>
  72. /// 根据条件判断是否允许生成HTML
  73. /// </summary>
  74. /// <param name="relativePath"></param>
  75. /// <returns></returns>
  76. protected bool IsBuildHtml(string relativePath)
  77. {
  78. if (MinInterval <= 0) return true;
  79. var path = Common.GetWebRootPath(relativePath);
  80. var fi = new FileInfo(path);
  81. if (!fi.Exists) return true;
  82. var time = fi.LastWriteTime.DateDiff(DateTime.Now);
  83. return time >= TimeSpan.FromSeconds(MinInterval);
  84. }
  85. /// <summary>
  86. /// 写HTML
  87. /// </summary>
  88. /// <param name="context"></param>
  89. /// <param name="stream"></param>
  90. protected async Task WriteHtml(ResultExecutingContext context, Stream stream)
  91. {
  92. stream.Position = 0;
  93. var responseReader = new StreamReader(stream);
  94. var responseContent = await responseReader.ReadToEndAsync();
  95. if (string.IsNullOrEmpty(responseContent)) return;
  96. var _logger = Web.GetService<ILogger<HtmlStaticAttribute>>();
  97. try
  98. {
  99. var path = Common.GetWebRootPath(RouteReplace(context, Template));
  100. FileHelper.Create(path, responseContent);
  101. }
  102. catch (Exception ex)
  103. {
  104. _logger.LogError(ex, "生成html静态文件失败");
  105. }
  106. }
  107. }

使用方法如下

  1. //在页面顶部增加标签,只要访问该页面就会生成HTML
  2. [HtmlStatic(MinInterval = 0, Template = "/static/{page}.html")]
  3. public class IndexModel : PageModel
  4. {
  5. public IndexModel()
  6. {
  7. }
  8. }
标签: Razor Pages NetCore

上一篇: EasyQuartz的使用,让后台任务合理并有序的执行

下一篇: jenkins docker 安装libgdiplus libc6-dev 支持Drawing组件 dockerfile配置

by 2023-08-07 23:49:06
篇笔记

学习笔记