title: Threading issue when using IViewComponentHelper to convert ViewComponent to HTML string
body: <p>I'm trying to use <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.iviewcomponenthelper?view=aspnetcore-3.1" rel="nofollow noreferrer">IViewComponentHelper</a> in a multi threaded manner. But it's throwing :( (see full exception at bottom of post)</p>
  <pre><code>System.NullReferenceException: Object reference not set to an instance of an object.
     at Microsoft.AspNetCore.Http.DefaultHttpContext.get_Items()
  </code></pre>
  <p>I have no idea why.</p>
  <p>Here's my setup: (simplified for brevity)</p>
  <p>Controller:</p>
  <pre class="lang-cs prettyprint-override"><code>public async Task&lt;IActionResult&gt; Get()
  {
      var pdfStream = await _pdfService.GenerateAsync();
  
      return File(pdfStream, &quot;application/pdf&quot;);
  }
  </code></pre>
  <p>PdfService:</p>
  <pre class="lang-cs prettyprint-override"><code>public class PdfService
  {
      private readonly PdfSDK _pdfSDK;
  
      public constructor(PdfSDK pdfSDK)
      {
          _pdfSDK = pdfSDK;
      }
  
      public async Task&lt;Stream&gt; GenerateAsync()
      {
          List&lt;Task&lt;PdfDocument&gt;&gt; tasks = await _context.Foos.Select(x =&gt; DoWorkAsync(x));
  
          var pdfs = await Task.WhenAll(tasks);
          var mergedPdf = _pdfSDK.MergePdfs(pdfs);
  
          return mergedPdf.Stream;
      }
  
      private async Task&lt;PdfDocument&gt; DoWorkAsync(Foo foo)
      {
          var html = await _renderViewComponentService.RenderViewComponentAsStringAsync&lt;MyViewComponent&gt;(foo);
          var document = await _pdfSDK.HtmlToDocumentAsync(html);
  
          return document;
      }
  }
  </code></pre>
  <p>PdfSDK:</p>
  <pre class="lang-cs prettyprint-override"><code>public class PdfSDK
  {
      public async Task&lt;PdfDocument&gt; HtmlToDocumentAsync(string html)
      {
          using var pdfEngine = new PdfEngine();
          var pdf = await pdfEngine.HtmlAsPdfAsync(html);
  
          return pdf;
      }
  
      public PdfDocument MergePdfs(params PdfDocument[] pdfs)
      {
          var pdf = PdfDocument.Merge(pdfs);
  
          return pdf;
      }
  }
  </code></pre>
  <p>MyViewComponent:</p>
  <pre class="lang-cs prettyprint-override"><code>public class MyViewComponent : ViewComponent
  {
      public IViewComponentResult Invoke(Foo args)
      {
          return View(args);
      }
  }
  </code></pre>
  <p>Default.cshtml</p>
  <pre><code>@model Foo
  
  &lt;h1&gt;Hello from @Foo.Id&lt;h1&gt;
  </code></pre>
  <p>RenderViewComponentService:</p>
  <pre class="lang-cs prettyprint-override"><code>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&lt;string&gt; RenderViewComponentAsStringAsync&lt;TViewComponent&gt;(object args)
          where TViewComponent : ViewComponent
      {
          var viewContext = GetFakeViewContext();
          (_viewComponentHelper as IViewContextAware).Contextualize(viewContext);
  
          // this appears to call InvokeAsync in TViewComponent, but it'll also call Invoke (synchronously) if it's implemented
          // see https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-3.1#perform-synchronous-work
          var htmlContent = await _viewComponentHelper.InvokeAsync&lt;TViewComponent&gt;(args); // exception is thrown here!
          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 =&gt; string.Empty;
          public Task RenderAsync(ViewContext context)
          {
              if (context == null) { throw new ArgumentNullException(nameof(context)); }
              return Task.CompletedTask;
          }
      }
  }
  </code></pre>
  <p>Exception:</p>
  <pre><code>fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
        An unhandled exception has occurred while executing the request.
  System.NullReferenceException: Object reference not set to an instance of an object.
     at Microsoft.AspNetCore.Http.DefaultHttpContext.get_Items()
     at Microsoft.AspNetCore.Mvc.Routing.UrlHelperFactory.GetUrlHelper(ActionContext context)
     at Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator.&lt;&gt;c__DisplayClass4_0.&lt;.ctor&gt;b__0(ViewContext context)
     at Microsoft.Extensions.Internal.PropertyActivator`1.Activate(Object instance, TContext context)
     at Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator.Activate(Object page, ViewContext context)
     at Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator.Activate(IRazorPage page, ViewContext context)
     at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
     at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)        
     at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
     at Microsoft.AspNetCore.Mvc.ViewComponents.ViewViewComponentResult.ExecuteAsync(ViewComponentContext context)
     at Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentInvoker.InvokeAsync(ViewComponentContext context)
     at Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentHelper.InvokeCoreAsync(ViewComponentDescriptor descriptor, Object arguments)
     at MyProject.Services.RenderViewComponentService.RenderViewComponentToStringAsync[TViewComponent](Object args) in MyProject\Services\RenderViewComponentService.cs:line ??
     at MyProject.Services.PdfService.DoWorkAsync(Foo foo) in MyProject\Services\PdfService.cs:line ??
     at MyProject.Services.PdfService.GenerateAsync() in MyProject\Services\PdfService.cs:line ??
     at MyProject.Controllers.MyController.Get() in MyProject\Controllers\MyController.cs:line ??
     at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
     at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeActionMethodAsync&gt;g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
     at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeNextActionFilterAsync&gt;g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
     at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
     at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State&amp; next, Scope&amp; scope, Object&amp; state, Boolean&amp; isCompleted)
     at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeInnerFilterAsync&gt;g__Awaited|13_0(ControllerActionInvoker 
  invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
     at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&lt;InvokeNextResourceFilter&gt;g__Awaited|24_0(ResourceInvoker invoker, Task 
  lastTask, State next, Scope scope, Object state, Boolean isCompleted)
     at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
     at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State&amp; next, Scope&amp; scope, Object&amp; state, Boolean&amp; isCompleted)    
     at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&lt;InvokeFilterPipelineAsync&gt;g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
     at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&lt;InvokeAsync&gt;g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
     at Microsoft.AspNetCore.Routing.EndpointMiddleware.&lt;Invoke&gt;g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
     at Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationMiddleware.Invoke(HttpContext context)
     at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
     at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
     at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
  </code></pre>
  <p>I think the problem is how I Contextualize the IViewComponentHelper. But I'm at a loss on how else to do it.</p>
  <p>Can you spot anything I'm doing wrong?</p>