首页 文章

将Razor页面渲染为字符串

提问于
浏览
10

问题:

我需要将Razor页面部分渲染为字符串 .

为什么我要这个:

我想创建一个控制器操作,该操作使用包含部分视图和其他可选参数的JSON进行响应 .

尝试:

我熟悉以下将View呈现为字符串的示例:https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs

但是,它与Pages不兼容,因为它只在Views目录中搜索,所以即使我给它一个绝对的路径,它试图找到我的_Layout.cshtml(它甚至不应该这样做!)并失败找到它 .

我试图修改它以便渲染页面,但是在尝试渲染时,我最终在部分中获得了ViewData的NullReferenceException . 我怀疑它与NullView有关,但我不知道该放在那里(RazorView的构造函数需要许多我不知道如何正确获取的对象) .

代码:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0: https://www.apache.org/licenses/LICENSE-2.0
// Modified by OronDF343: Uses pages instead of views.

using System;
using System.IO;
using System.Linq;
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.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Routing;

namespace TestAspNetCore.Services
{
    public class RazorPageToStringRenderer
    {
        private readonly IRazorViewEngine _viewEngine;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IServiceProvider _serviceProvider;

        public RazorPageToStringRenderer(
            IRazorViewEngine viewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider)
        {
            _viewEngine = viewEngine;
            _tempDataProvider = tempDataProvider;
            _serviceProvider = serviceProvider;
        }

        public async Task<string> RenderPageToStringAsync<TModel>(string viewName, TModel model)
        {
            var actionContext = GetActionContext();
            var page = FindPage(actionContext, viewName);

            using (var output = new StringWriter())
            {
                var viewContext = new ViewContext(actionContext,
                                                  new NullView(),
                                                  new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(),
                                                                                 new ModelStateDictionary())
                                                  {
                                                      Model = model
                                                  },
                                                  new TempDataDictionary(actionContext.HttpContext,
                                                                         _tempDataProvider),
                                                  output,
                                                  new HtmlHelperOptions());

                page.ViewContext = viewContext;
                await page.ExecuteAsync();

                return output.ToString();
            }
        }

        private IRazorPage FindPage(ActionContext actionContext, string pageName)
        {
            var getPageResult = _viewEngine.GetPage(executingFilePath: null, pagePath: pageName);
            if (getPageResult.Page != null)
            {
                return getPageResult.Page;
            }

            var findPageResult = _viewEngine.FindPage(actionContext, pageName);
            if (findPageResult.Page != null)
            {
                return findPageResult.Page;
            }

            var searchedLocations = getPageResult.SearchedLocations.Concat(findPageResult.SearchedLocations);
            var errorMessage = string.Join(
                Environment.NewLine,
                new[] { $"Unable to find page '{pageName}'. The following locations were searched:" }.Concat(searchedLocations));

            throw new InvalidOperationException(errorMessage);
        }

        private ActionContext GetActionContext()
        {
            var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
            return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
        }
    }
}

2 回答

  • 2

    这就是我做到的 .

    始终在Startup.cs中注册服务

    services.AddScoped<IViewRenderService, ViewRenderService>();
    

    该服务定义如下:

    public interface IViewRenderService
    {
        Task<string> RenderToStringAsync<T>(string viewName, T model) where T : PageModel;
    }
    
    public class ViewRenderService : IViewRenderService
    {
        private readonly IRazorViewEngine _razorViewEngine;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IServiceProvider _serviceProvider;
        private readonly IHttpContextAccessor _httpContext;
        private readonly IActionContextAccessor _actionContext;
        private readonly IRazorPageActivator _activator;
    
    
        public ViewRenderService(IRazorViewEngine razorViewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider,
            IHttpContextAccessor httpContext,
            IRazorPageActivator activator,
            IActionContextAccessor actionContext)
        {
            _razorViewEngine = razorViewEngine;
            _tempDataProvider = tempDataProvider;
            _serviceProvider = serviceProvider;
    
            _httpContext = httpContext;
            _actionContext = actionContext;
            _activator = activator;
    
        }
    
    
        public async Task<string> RenderToStringAsync<T>(string pageName, T model) where T : PageModel
        {
    
    
            var actionContext =
                new ActionContext(
                    _httpContext.HttpContext,
                    _httpContext.HttpContext.GetRouteData(),
                    _actionContext.ActionContext.ActionDescriptor
                );
    
            using (var sw = new StringWriter())
            {
                var result = _razorViewEngine.FindPage(actionContext, pageName);
    
                if (result.Page == null)
                {
                    throw new ArgumentNullException($"The page {pageName} cannot be found.");
                }
    
                var view = new RazorView(_razorViewEngine,
                    _activator,
                    new List<IRazorPage>(),
                    result.Page,
                    HtmlEncoder.Default,
                    new DiagnosticListener("ViewRenderService"));
    
    
                var viewContext = new ViewContext(
                    actionContext,
                    view,
                    new ViewDataDictionary<T>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                    {
                        Model = model
                    },
                    new TempDataDictionary(
                        _httpContext.HttpContext,
                        _tempDataProvider
                    ),
                    sw,
                    new HtmlHelperOptions()
                );
    
    
                var page = ((Page)result.Page);
    
                page.PageContext = new Microsoft.AspNetCore.Mvc.RazorPages.PageContext
                {
                    ViewData = viewContext.ViewData
    
                };
    
                page.ViewContext = viewContext;
    
    
                _activator.Activate(page, viewContext);
    
                await page.ExecuteAsync();
    
    
                return sw.ToString();
            }
        }
    
    
    
    }
    

    我这样称呼它

    emailView.Body = await this._viewRenderService.RenderToStringAsync("Email/ConfirmAccount", new Email.ConfirmAccountModel
                    {
                        EmailView = emailView,
                    });
    

    “Email / ConfirmAccount”是我的Razor页面的路径(在页面下) . “ConfirmAccountModel”是该页面的页面模型 .

    ViewData为null,因为在设置PageContext时设置了Page的ViewData,因此如果未设置,则ViewData为null .

    我还发现我不得不打电话

    _activator.Activate(page, viewContext);
    

    为了这一切工作 . 这尚未经过全面测试,因此可能无法适用于所有场景,但应该可以帮助您入门 .

  • 6

    我有同样的问题 .

    我查看了RazorViewEngine源代码,发现使用“page”路由数据搜索页面:

    var routeData = new RouteData();
    routeData.Values.Add("page", "/Folder/MyPage");
    

    这对我来说是路径数据中的完整路径“/ Folder / MyPage”,以及GetPage调用中的页面名称“MyPage” .

相关问题