在 ASP.NET Core 2.x 中手动创建 HttpContext

Bra*_*rad 9 c# httpcontext asp.net-core-mvc asp.net-core

我正在尝试将 Razor 视图呈现为来自托管服务的字符串。通过使用,IRazorViewEngine我可以使用如下所示的方式将视图呈现为字符串:

 _viewEngine.FindView(actionContext, viewName, false);
var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = model
                };

                var viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewDictionary,
                    new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                    sw,
                    new HtmlHelperOptions()
                );
                viewContext.RouteData = httpContext.GetRouteData();   //set route data here

                await viewResult.View.RenderAsync(viewContext);
Run Code Online (Sandbox Code Playgroud)

但是,当Controller由于缺少而没有从 a 调用时,这会崩溃HttpContext。我尝试手动构建 HttpContext,但在 Microsoft Mvc 代码深处遇到许多错误和空异常,这极难调试。我尝试过像RazorLight这样的库,它不适合我的需求,因为它不能正确支持@inject指令。我认为我最好的解决方案是尝试模拟一个假的 HttpContext/ControllerContext 以传递给本机 ViewEngine。但是,当我创建一个 new 时DefaultHttpContext,我在此处得到一个 NullReferenceException ,但是很难跟踪代码并找到它的来源。

有没有办法创建一个新的 HttpContext?

小智 6

您可以通过创建一个 来模拟它DefaultHttpContext,但是 MVC 需要一些不存在于根 DI 范围中的范围服务,因此您必须ServiceProvider为您的渲染创建一个范围。

这是一个IHostedService呈现视图的示例(我确实在带有 MVC 的 WebApplication 模板中运行了它):

public class ViewRenderService : IHostedService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;

    public ViewRenderService(IRazorViewEngine razorViewEngine,
        ITempDataProvider tempDataProvider,
        IServiceProvider serviceProvider)
    {
        _razorViewEngine = razorViewEngine;
        _tempDataProvider = tempDataProvider;
        _serviceProvider = serviceProvider;
    }

    public async Task<string> RenderToStringAsync(string viewName, object model)
    {
        using (var requestServices = _serviceProvider.CreateScope())
        {
            var httpContext = new DefaultHttpContext { RequestServices = requestServices.ServiceProvider };
            var routeData = new RouteData();
            routeData.Values.Add("controller", "Home");
            var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());

            using (var sw = new StringWriter())
            {
                var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);

                if (viewResult.View == null)
                {
                    throw new ArgumentNullException($"{viewName} does not match any available view");
                }

                var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = model
                };

                var viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewDictionary,
                    new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                    sw,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);
                return sw.ToString();
            }
        }
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        var html = await RenderToStringAsync("About", null);
        return;
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:此示例基于在此处找到的博客文章,但经过修改以在IHostedService. https://ppolyzos.com/2016/09/09/asp-net-core-render-view-to-string/