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; } + } +}