zoukankan      html  css  js  c++  java
  • ASP.NET Core Razor生成Html静态文件

    一、前言

    最近做项目的时候,使用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

  • 相关阅读:
    美国在线CEO:雅虎被Verizon收购或导致裁员
    美国在线CEO:雅虎被Verizon收购或导致裁员
    在CentOS 7中安装配置JDK8
    在CentOS 7中安装配置JDK8
    在CentOS 7中安装配置JDK8
    在CentOS 7中安装配置JDK8
    库克再访华受到深圳书记市长接待 要建研发中心
    库克再访华受到深圳书记市长接待 要建研发中心
    他变行商为坐商,打造天津港屈指可数的民营运输企业
    放弃市场经理位置,小伙搞医药策划实现创业梦想
  • 原文地址:https://www.cnblogs.com/jianxuanbing/p/9183359.html
Copyright © 2011-2022 走看看