diff --git a/sample/Util.Samples.Webs/Apis/Tools/InitRazorController.cs b/sample/Util.Samples.Webs/Apis/Tools/InitRazorController.cs
new file mode 100644
index 00000000..1f4be586
--- /dev/null
+++ b/sample/Util.Samples.Webs/Apis/Tools/InitRazorController.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Util.Helpers;
+using Util.Webs.Controllers;
+using Util.Webs.Razors;
+
+namespace Util.Samples.Webs.Apis.Tools
+{
+ ///
+ /// 初始化Razor控制器
+ ///
+ public class InitRazorController : WebApiControllerBase
+ {
+ ///
+ /// Razor静态Html生成器
+ ///
+ public IRazorHtmlGenerator RazorHtmlGenerator { get; set; }
+
+ ///
+ /// 初始化一个类型的实例
+ ///
+ ///
+ public InitRazorController(IRazorHtmlGenerator razorHtmlGenerator)
+ {
+ RazorHtmlGenerator = razorHtmlGenerator;
+ }
+
+ ///
+ /// 初始化路由
+ ///
+ ///
+ [Route("/api/init")]
+ [HttpPost]
+ public async Task Init()
+ {
+ await RazorHtmlGenerator.Generate();
+ return new JsonResult("");
+ }
+ }
+}
diff --git a/sample/Util.Samples.Webs/Startup.cs b/sample/Util.Samples.Webs/Startup.cs
index 8f776dcc..da1ad27b 100644
--- a/sample/Util.Samples.Webs/Startup.cs
+++ b/sample/Util.Samples.Webs/Startup.cs
@@ -63,6 +63,9 @@ namespace Util.Samples.Webs {
options.IncludeXmlComments( Path.Combine( AppContext.BaseDirectory, "Util.Samples.Webs.xml" ) );
} );
+ // 添加Razor静态Html生成器
+ services.AddRazorHtml();
+
//添加Util基础设施服务
return services.AddUtil();
}
diff --git a/src/Util.Webs/Extensions/Extensions.Service.cs b/src/Util.Webs/Extensions/Extensions.Service.cs
new file mode 100644
index 00000000..d67f597d
--- /dev/null
+++ b/src/Util.Webs/Extensions/Extensions.Service.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.Extensions.DependencyInjection;
+using Util.Webs.Razors;
+
+namespace Util.Webs.Extensions
+{
+ ///
+ /// 服务扩展
+ ///
+ public static partial class Extensions
+ {
+ ///
+ /// 注册Razor静态Html生成器
+ ///
+ ///
+ ///
+ public static IServiceCollection AddRazorHtml(this IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddScoped();
+ return services;
+ }
+ }
+}
diff --git a/src/Util.Webs/Razors/DefaultRazorHtmlGenerator.cs b/src/Util.Webs/Razors/DefaultRazorHtmlGenerator.cs
new file mode 100644
index 00000000..edfd0e70
--- /dev/null
+++ b/src/Util.Webs/Razors/DefaultRazorHtmlGenerator.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Abstractions;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Mvc.Razor;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+using Microsoft.AspNetCore.Routing;
+using Util.Helpers;
+using Util.Logs;
+using Util.Logs.Extensions;
+
+namespace Util.Webs.Razors
+{
+ ///
+ /// Razor静态Html生成器
+ ///
+ public class DefaultRazorHtmlGenerator:IRazorHtmlGenerator
+ {
+ private readonly IRouteAnalyzer _routeAnalyzer;
+
+ ///
+ /// 初始化一个类型的实例
+ ///
+ /// 路由分析器
+ public DefaultRazorHtmlGenerator(IRouteAnalyzer routeAnalyzer)
+ {
+ _routeAnalyzer = routeAnalyzer;
+ }
+
+ ///
+ /// 生成Html文件
+ ///
+ ///
+ public async Task Generate()
+ {
+ foreach (var routeInformation in _routeAnalyzer.GetAllRouteInformations())
+ {
+ // 跳过API的处理
+ if (routeInformation.Path.StartsWith("/api"))
+ {
+ continue;
+ }
+ await WriteViewToFileAsync(routeInformation);
+ }
+ }
+
+ ///
+ /// 渲染视图为字符串
+ ///
+ /// 路由信息
+ ///
+ public async Task RenderToStringAsync(RouteInformation info)
+ {
+ var razorViewEngine = Ioc.Create();
+ var tempDataProvider = Ioc.Create();
+ var serviceProvider = Ioc.Create();
+
+ 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();
+ }
+ }
+
+ ///
+ /// 将视图写入文件
+ ///
+ /// 路由信息
+ ///
+ public async Task WriteViewToFileAsync(RouteInformation info)
+ {
+ try
+ {
+ var html = await RenderToStringAsync(info);
+ if (string.IsNullOrWhiteSpace(html))
+ return;
+
+ var path = Util.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();
+ }
+ }
+}
diff --git a/src/Util.Webs/Razors/IRazorHtmlGenerator.cs b/src/Util.Webs/Razors/IRazorHtmlGenerator.cs
new file mode 100644
index 00000000..074dddf7
--- /dev/null
+++ b/src/Util.Webs/Razors/IRazorHtmlGenerator.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Util.Webs.Razors
+{
+ ///
+ /// Razor Html生成器
+ ///
+ public interface IRazorHtmlGenerator
+ {
+ ///
+ /// 生成Html文件
+ ///
+ ///
+ Task Generate();
+ }
+}
diff --git a/src/Util.Webs/Razors/IRouteAnalyzer.cs b/src/Util.Webs/Razors/IRouteAnalyzer.cs
new file mode 100644
index 00000000..fbeedd4e
--- /dev/null
+++ b/src/Util.Webs/Razors/IRouteAnalyzer.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Util.Webs.Razors
+{
+ ///
+ /// 路由分析器
+ ///
+ public interface IRouteAnalyzer
+ {
+ ///
+ /// 获取所有路由信息
+ ///
+ ///
+ IEnumerable GetAllRouteInformations();
+ }
+}
diff --git a/src/Util.Webs/Razors/RouteAnalyzer.cs b/src/Util.Webs/Razors/RouteAnalyzer.cs
new file mode 100644
index 00000000..4bf71856
--- /dev/null
+++ b/src/Util.Webs/Razors/RouteAnalyzer.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Util.Webs.Filters;
+
+namespace Util.Webs.Razors
+{
+ ///
+ /// 路由分析器
+ ///
+ public class RouteAnalyzer:IRouteAnalyzer
+ {
+ ///
+ /// 操作描述集合提供程序
+ ///
+ private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
+
+ ///
+ /// 初始化一个类型的实例
+ ///
+ /// 操作描述集合提供程序
+ public RouteAnalyzer(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
+ {
+ _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
+ }
+
+ ///
+ /// 获取所有路由信息
+ ///
+ ///
+ public IEnumerable GetAllRouteInformations()
+ {
+ List list=new List();
+
+ 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();
+
+ if (controllerHtmlAttribute != null)
+ {
+ info.FilePath = controllerHtmlAttribute.Path;
+ info.TemplatePath = controllerHtmlAttribute.Template;
+ }
+
+ var htmlAttribute = controllerActionDescriptor.MethodInfo.GetCustomAttribute();
+
+ 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;
+ }
+ }
+}
diff --git a/src/Util.Webs/Razors/RouteInformation.cs b/src/Util.Webs/Razors/RouteInformation.cs
new file mode 100644
index 00000000..46bf70c9
--- /dev/null
+++ b/src/Util.Webs/Razors/RouteInformation.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Util.Webs.Razors
+{
+ ///
+ /// 路由信息
+ ///
+ public class RouteInformation
+ {
+ ///
+ /// 区域名称
+ ///
+ public string AreaName { get; set; }
+
+ ///
+ /// 控制器名称
+ ///
+ public string ControllerName { get; set; }
+
+ ///
+ /// 操作名称
+ ///
+ public string ActionName { get; set; }
+
+ ///
+ /// Razor页面路径
+ ///
+ public string Path { get; set; }
+
+ ///
+ /// 调用方法
+ ///
+ public string Invocation { get; set; }
+
+ ///
+ /// 文件路径
+ ///
+ public string FilePath { get; set; }
+
+ ///
+ /// 模板路径
+ ///
+ public string TemplatePath { get; set; }
+ }
+}