[英]Programmatically render Razor Page as HTML string
IsDevelopment()
時才會公開。我正在使用 ASP.NET 核心 3.1
我想我會嘗試新的 Razor 頁面,因為它們被宣傳為超級簡單。
@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();
}
}
}
這適用於瀏覽器 - 耶!
我發現Render a Razor Page to string這似乎可以滿足我的要求。
但這就是麻煩開始的地方:(
首先,我覺得很奇怪,當您通過_razorViewEngine.FindPage
找到頁面時,它不知道如何填充ViewContext
或Model
。 我認為IndexModel
的工作是填充這些。 我希望可以向 ASP.NET 詢問IndexModel
頁面,就是這樣。
無論如何……下一個問題。 為了呈現頁面,我必須手動創建ViewContext
並且必須為其提供Model
。 但是 Page 是 Model,因為它是一個 Page,所以它不是一個簡單的 ViewModel。 它依賴於 DI,並希望執行OnGetAsync()
以填充 Model。 這幾乎是第 22 條規則。
我還嘗試通過_razorViewEngine.FindView
獲取視圖而不是頁面,但這也需要Model,所以我們回到catch-22。
另一個問題。 調試/調整頁面的目的是輕松查看生成的內容。 但是,如果我必須在Model
IndexModel
那么它不再代表某處服務中實際生成的內容。
這一切都讓我想知道我是否走在正確的道路上。 還是我錯過了什么?
請參考以下步驟將部分視圖渲染為字符串:
將接口添加到名為 IRazorPartialToStringRenderer.cs 的 Services 文件夾。
public interface IRazorPartialToStringRenderer { Task<string> RenderPartialToStringAsync<TModel>(string partialName, TModel model); }
使用以下代碼將 C# class 文件添加到名為 RazorPartialToStringRenderer.cs 的服務文件夾中:
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()); } } }
在Startup
class 的ConfigureServices
方法中注冊服務:
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddTransient<IRazorPartialToStringRenderer, RazorPartialToStringRenderer>(); }
使用 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 。
我設法破解它。 畢竟我走錯了路……解決方案是使用ViewComponent
。 但它仍然很時髦!
謝謝
namespace MyProject.ViewComponents
{
public class MyViewComponent : ViewComponent
{
private readonly MyDbContext _context;
public MyViewComponent(MyDbContext context)
{
_context = context;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var count = await _context.Foos.CountAsync();
var message = $"Server time is { DateTime.Now } and the Foo count is { count }";
return View<string>(message);
}
}
}
並且視圖放置在Pages/Shared/Components/My/Default.cshtml
@model string
<h2>Test</h2>
<p>
@Model
</p>
using System;
using System.IO;
using System.Text.Encodings.Web;
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.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
public class RenderViewComponentService
{
private readonly IServiceProvider _serviceProvider;
private readonly ITempDataProvider _tempDataProvider;
private readonly IViewComponentHelper _viewComponentHelper;
public RenderViewComponentService(
IServiceProvider serviceProvider,
ITempDataProvider tempDataProvider,
IViewComponentHelper viewComponentHelper
)
{
_serviceProvider = serviceProvider;
_tempDataProvider = tempDataProvider;
_viewComponentHelper = viewComponentHelper;
}
public async Task<string> RenderViewComponentToStringAsync<TViewComponent>(object args)
where TViewComponent : ViewComponent
{
var viewContext = GetFakeViewContext();
(_viewComponentHelper as IViewContextAware).Contextualize(viewContext);
var htmlContent = await _viewComponentHelper.InvokeAsync<TViewComponent>(args);
using var stringWriter = new StringWriter();
htmlContent.WriteTo(stringWriter, HtmlEncoder.Default);
var html = stringWriter.ToString();
return html;
}
private ViewContext GetFakeViewContext(ActionContext actionContext = null, TextWriter writer = null)
{
actionContext ??= GetFakeActionContext();
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
var tempData = new TempDataDictionary(actionContext.HttpContext, _tempDataProvider);
var viewContext = new ViewContext(
actionContext,
NullView.Instance,
viewData,
tempData,
writer ?? TextWriter.Null,
new HtmlHelperOptions());
return viewContext;
}
private ActionContext GetFakeActionContext()
{
var httpContext = new DefaultHttpContext
{
RequestServices = _serviceProvider,
};
var routeData = new RouteData();
var actionDescriptor = new ActionDescriptor();
return new ActionContext(httpContext, routeData, actionDescriptor);
}
private class NullView : IView
{
public static readonly NullView Instance = new NullView();
public string Path => string.Empty;
public Task RenderAsync(ViewContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
return Task.CompletedTask;
}
}
}
注意文件后面沒有代碼
@page
@using MyProject.ViewComponents
@await Component.InvokeAsync(typeof(MyViewComponent))
@page "{id}"
@using MyProject.ViewComponents
@await Component.InvokeAsync(typeof(MyViewComponent), RouteData.Values["id"])
[HttpGet]
public async Task<IActionResult> Get()
{
var html = await _renderViewComponentService
.RenderViewComponentToStringAsync<MyViewComponent>();
// do something with the html
return Ok(new { html });
}
[HttpGet("{id}")]
public async Task<IActionResult> Get([FromRoute] int id)
{
var html = await _renderViewComponentService
.RenderViewComponentToStringAsync<MyViewComponent>(id);
// do something with the html
return Ok(new { html });
}
非常不幸的是,注入的IViewComponentHelper
不能開箱即用。
所以我們做了這個非常不直觀的事情來讓它工作。
(_viewComponentHelper as IViewContextAware).Contextualize(viewContext);
這會導致一系列奇怪的事情,例如需要TextWriter
的虛假ActionContext
和ViewContext
,但它不用於任何事情! 事實上,根本沒有使用ViewContext
洞。 它只需要存在:(
此外NullView
...由於某種原因Microsoft.AspNetCore.Mvc.ViewFeatures.NullView
是Internal
的,所以我們基本上必須將其復制/粘貼到我們自己的代碼中。
也許將來會有所改進。
無論如何:IMO這比使用IRazorViewEngine
更簡單,幾乎每次web搜索都會出現:)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.