PageModel静态化方案查找过很多资料。
基本上没找到一个使用视图引擎静态化PageModel的方案。
最终也只是解决了分部视图静态化方式,对于Layout也是无能为力。
以下实现了PageModel扩展实现了对视图渲染转换成Html。此方法不包含Layout和Section。
/// <summary>
/// 转换成Html
/// </summary>
/// <param name="pageModel"></param>
/// <param name="pageName"></param>
/// <returns></returns>
public async Task<string> ToHtml(this PageModel pageModel, string pageName)
{
var actionContext = new ActionContext(
pageModel.HttpContext,
pageModel.RouteData,
pageModel.PageContext.ActionDescriptor
);
using (var sw = new StringWriter())
{
IRazorViewEngine _razorViewEngine = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorViewEngine)) as IRazorViewEngine;
IRazorPageActivator _activator = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorPageActivator)) as IRazorPageActivator;
var result = _razorViewEngine.FindPage(actionContext, pageName);
if (result.Page == null)
{
throw new ArgumentNullException($"The page {pageName} cannot be found.");
}
var page = result.Page;
var view = new RazorView(_razorViewEngine,
_activator,
new List<IRazorPage>(),
page,
HtmlEncoder.Default,
new DiagnosticListener("ViewRenderService"));
var viewContext = new ViewContext(
actionContext,
view,
pageModel.ViewData,
pageModel.TempData,
sw,
new HtmlHelperOptions()
);
var pageNormal = ((Page)result.Page);
pageNormal.PageContext = pageModel.PageContext;
pageNormal.ViewContext = viewContext;
_activator.Activate(pageNormal, viewContext);
await page.ExecuteAsync();
return sw.ToString();
}
}
但是由于某些原因,我们需要生成包含layout的整个HTML页面,所以暂时没有别的办法,只能通过ActionFilter
拦截的方式获取生成后的HTML,如下方代码
/// <summary>
/// Razor生成Html静态文件(保存目录为wwwroot)
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class HtmlStaticAttribute : ActionFilterAttribute
{
/// <summary>
/// 路径模板,范例:static/{area}/{controller}/{action}.component.html
/// </summary>
public string Template { get; set; }
/// <summary>
/// 生成的最小间隔,单位(秒),比如设置5分钟,那么5分钟之内不会再生成
/// </summary>
public int MinInterval { get; set; } = 0;
/// <summary>
/// 结果执行之前 before
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
if ((context.Result is PageResult || context.Result is ViewResult) && IsBuildHtml(RouteReplace(context, Template)))
{
var response = context.HttpContext.Response;
if (!response.Body.CanRead || !response.Body.CanSeek)
{
using (var ms = new MemoryStream())
{
var old = response.Body;
response.Body = ms;
await base.OnResultExecutionAsync(context, next);
if (response.StatusCode == 200)
{
await WriteHtml(context, response.Body);
}
ms.Position = 0;
await ms.CopyToAsync(old);
response.Body = old;
}
}
else
{
await base.OnResultExecutionAsync(context, next);
var old = response.Body.Position;
if (response.StatusCode == 200)
{
await WriteHtml(context, response.Body);
}
response.Body.Position = old;
}
}
else
{
await base.OnResultExecutionAsync(context, next);
}
}
/// <summary>
/// 根据路由参数进行模板替换
/// </summary>
/// <param name="context"></param>
/// <param name="template">比如:static/{area}/{controller}/{action}/{id}.html</param>
/// <returns></returns>
public static string RouteReplace(ActionContext context, string template)
{
var path = template;
foreach (var route in context.RouteData.Values)
path = path.Replace("{" + route.Key + "}", route.Value.SafeString());
return path.ToLower();
}
/// <summary>
/// 根据条件判断是否允许生成HTML
/// </summary>
/// <param name="relativePath"></param>
/// <returns></returns>
protected bool IsBuildHtml(string relativePath)
{
if (MinInterval <= 0) return true;
var path = Common.GetWebRootPath(relativePath);
var fi = new FileInfo(path);
if (!fi.Exists) return true;
var time = fi.LastWriteTime.DateDiff(DateTime.Now);
return time >= TimeSpan.FromSeconds(MinInterval);
}
/// <summary>
/// 写HTML
/// </summary>
/// <param name="context"></param>
/// <param name="stream"></param>
protected async Task WriteHtml(ResultExecutingContext context, Stream stream)
{
stream.Position = 0;
var responseReader = new StreamReader(stream);
var responseContent = await responseReader.ReadToEndAsync();
if (string.IsNullOrEmpty(responseContent)) return;
var _logger = Web.GetService<ILogger<HtmlStaticAttribute>>();
try
{
var path = Common.GetWebRootPath(RouteReplace(context, Template));
FileHelper.Create(path, responseContent);
}
catch (Exception ex)
{
_logger.LogError(ex, "生成html静态文件失败");
}
}
}
使用方法如下
//在页面顶部增加标签,只要访问该页面就会生成HTML
[HtmlStatic(MinInterval = 0, Template = "/static/{page}.html")]
public class IndexModel : PageModel
{
public IndexModel()
{
}
}