Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 697
Brad Avatar asked Dec 04 '18 03:12

Brad


2 Answers

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/

like image 109
hiiru Avatar answered Nov 17 '22 15:11

hiiru


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();
}
like image 1
TanvirArjel Avatar answered Nov 17 '22 15:11

TanvirArjel