Manually creating an HttpContext in ASP.NET Core 2.x

删除回忆录丶 提交于 2021-02-07 14:43:38

问题


I'm trying to render a Razor view to a string from a Hosted Service. By using the IRazorViewEngine I am able to render a view to a string using something like the following:

 _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);

However this falls apart when it is not called from a Controller due to missing HttpContext. I've tried building an HttpContext manually, but I get many errors and null exceptions deep in the Microsoft Mvc code which is extremely hard to debug. I've tried libraries like RazorLight which don't suit my needs because it doesn't properly support the @inject directive. I think my best solution is to try and mock up a fake HttpContext/ControllerContext to pass to the native ViewEngine. However, when I create a new DefaultHttpContext, I get a NullReferenceException around here, but it's very hard to trace the code and find where it is coming from.

Is there any way to create a new HttpContext?


回答1:


You can mock it by creating a DefaultHttpContext, however MVC requires some scoped services not present in the root DI scope, so you have to create a ServiceProvider scope for your rendering.

Here is a sample IHostedService that renders a view (I did run it in the WebApplication template with MVC):

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)
    {
    }
}

Note: This sample is based on a blog post found here, but modified to work in IHostedService. https://ppolyzos.com/2016/09/09/asp-net-core-render-view-to-string/




回答2:


Try this:

public class YourClass 
{
   private readonly IHttpContextAccessor _httpContextAccessor;
   public YourClass(IHttpContextAccessor httpContextAccessor)
   {
      _httpContextAccessor = httpContextAccessor;
   }

   public void YourMethod()
   {
      // access HttpContext with __httpContextAccessor.HttpContext
   }
}

And then register IHttpContextAccessor in the Startup class as follows:

public void ConfigureServices(IServiceCollection services)
{
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    // Or you can also register as follows

    services.AddHttpContextAccessor();
}


来源:https://stackoverflow.com/questions/53605268/manually-creating-an-httpcontext-in-asp-net-core-2-x

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!