首页 > 解决方案 > 以编程方式将 Razor 页面呈现为 HTML 字符串

问题描述

目标

我正在使用 ASP.NET Core 3.1

方法

剃刀页面

我想我会尝试的Razor Pages,因为它们被宣传为超级简单。

@page
@using MyProject.Pages.Pdf
@model IndexModel

<h2>Test</h2>
<p>
    @Model.Message
</p>
namespace MyProject.Pages.Pdf
{
    public class IndexModel : PageModel
    {
        private readonly MyDbContext _context;

        public IndexModel(MyDbContext context)
        {
            _context = context;
        }

        public string Message { get; private set; } = "PageModel in C#";

        public async Task<IActionResult> OnGetAsync()
        {
            var count = await _context.Foos.CountAsync();

            Message += $" Server time is { DateTime.Now } and the Foo count is { count }";

            return Page();
        }
    }
}

这适用于浏览器 - 耶!

渲染并获取 HTML 字符串

我发现Render a Razor Page to string这似乎可以满足我的要求。

但这就是麻烦开始的地方:(

问题

首先,我觉得很奇怪的是,当您通过_razorViewEngine.FindPage它找到页面时不知道如何填充ViewContextModel. 我认为的工作IndexModel是填充这些。我希望可以向 ASP.NET 询问IndexModel页面,就是这样。

无论如何……下一个问题。为了呈现页面,我必须手动创建一个ViewContext并且我必须为它提供一个Model. 但是 Page 是 Model,因为它是一个 Page,所以它不是一个简单的 ViewModel。它依赖于 DI,并期望OnGetAsync()被执行以填充模型。这几乎是第 22 条规则。

我还尝试通过获取视图而不是页面,_razorViewEngine.FindView但这也需要模型,所以我们回到了 catch-22。

另一个问题。调试/调整页面的目的是轻松查看生成的内容。但是,如果我必须创建一个Model外部IndexModel,那么它就不再代表某处服务中实际生成的内容。

这一切都让我想知道我是否走在正确的道路上。还是我错过了什么?

标签: c#asp.net-corerazor-pagesasp.net-core-3.1razorengine

解决方案


请参考以下步骤将部分视图渲染为字符串:

  1. 将接口添加到名为 IRazorPartialToStringRenderer.cs 的 Services 文件夹。

     public interface IRazorPartialToStringRenderer
     {
         Task<string> RenderPartialToStringAsync<TModel>(string partialName, TModel model);
     }
    
  2. 使用以下代码将 C# 类文件添加到名为 RazorPartialToStringRenderer.cs 的 Services 文件夹中:

     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.ViewEngines;
     using Microsoft.AspNetCore.Mvc.ViewFeatures;
     using Microsoft.AspNetCore.Routing;
    
     namespace RazorPageSample.Services
     {
         public class RazorPartialToStringRenderer : IRazorPartialToStringRenderer
         {
             private IRazorViewEngine _viewEngine;
             private ITempDataProvider _tempDataProvider;
             private IServiceProvider _serviceProvider;
             public RazorPartialToStringRenderer(
                 IRazorViewEngine viewEngine,
                 ITempDataProvider tempDataProvider,
                 IServiceProvider serviceProvider)
             {
                 _viewEngine = viewEngine;
                 _tempDataProvider = tempDataProvider;
                 _serviceProvider = serviceProvider;
             }
             public async Task<string> RenderPartialToStringAsync<TModel>(string partialName, TModel model)
             {
                 var actionContext = GetActionContext();
                 var partial = FindView(actionContext, partialName);
                 using (var output = new StringWriter())
                 {
                     var viewContext = new ViewContext(
                         actionContext,
                         partial,
                         new ViewDataDictionary<TModel>(
                             metadataProvider: new EmptyModelMetadataProvider(),
                             modelState: new ModelStateDictionary())
                         {
                             Model = model
                         },
                         new TempDataDictionary(
                             actionContext.HttpContext,
                             _tempDataProvider),
                         output,
                         new HtmlHelperOptions()
                     );
                     await partial.RenderAsync(viewContext);
                     return output.ToString();
                 }
             }
             private IView FindView(ActionContext actionContext, string partialName)
             {
                 var getPartialResult = _viewEngine.GetView(null, partialName, false);
                 if (getPartialResult.Success)
                 {
                     return getPartialResult.View;
                 }
                 var findPartialResult = _viewEngine.FindView(actionContext, partialName, false);
                 if (findPartialResult.Success)
                 {
                     return findPartialResult.View;
                 }
                 var searchedLocations = getPartialResult.SearchedLocations.Concat(findPartialResult.SearchedLocations);
                 var errorMessage = string.Join(
                     Environment.NewLine,
                     new[] { $"Unable to find partial '{partialName}'. 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());
             }
         }
     }
    
  3. ConfigureServices在类的方法中注册服务Startup

     public void ConfigureServices(IServiceCollection services)
     {
         services.AddRazorPages(); 
         services.AddTransient<IRazorPartialToStringRenderer, RazorPartialToStringRenderer>();
     }
    
  4. 使用 RenderPartialToStringAsync() 方法将 Razor 页面呈现为 HTML 字符串:

     public class ContactModel : PageModel
     {
         private readonly IRazorPartialToStringRenderer _renderer;
         public ContactModel(IRazorPartialToStringRenderer renderer)
         {
             _renderer = renderer; 
         }
         public void OnGet()
         { 
         }
         [BindProperty]
         public ContactForm ContactForm { get; set; }
         [TempData]
         public string PostResult { get; set; }
    
         public async Task<IActionResult> OnPostAsync()
         {
             var body = await _renderer.RenderPartialToStringAsync("_ContactEmailPartial", ContactForm);  //transfer model to the partial view, and then render the Partial view to string.
             PostResult = "Check your specified pickup directory";
             return RedirectToPage();
         }
     }
     public class ContactForm
     {
         public string Email { get; set; }
         public string Message { get; set; }
         public string Name { get; set; }
         public string Subject { get; set; }
         public Priority Priority { get; set; }
     }
     public enum Priority
     {
         Low, Medium, High
     }
    

调试截图如下:

在此处输入图像描述

更多详细步骤,请查看此博客Rendering A Partial View To A String


推荐阅读