aspnetcore: HttpContext property of HttpContextAccessor is null

httpcontextaccessor_problem I have a REST API web application which worked perfectly with ASP.Net Core 2.1. Now the constructor injected IHttpContextAccessor returns a null value for HttpContext property.

In Startup.cs class, in ConfigureServices method I have:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();          
services.AddScoped<WorklistPagedTasksHalRepresentationConverter>();

This is the WorklistPagedTasksHalRepresentationConverter class

public class WorklistPagedTasksHalRepresentationConverter : TypeConverter<WorklistWithPagedTasks, WorklistPagedTasksHalRepresentation>
    {
        private readonly IHttpContextAccessor httpContextAccessor;

        private readonly IMapper mapper;

        public WorklistPagedTasksHalRepresentationConverter(IHttpContextAccessor httpContextAccessor, IMapper mapper)
        {
            this.httpContextAccessor = httpContextAccessor;
            this.mapper = mapper;
        }

        public WorklistPagedTasksHalRepresentation Convert(WorklistWithPagedTasks source, WorklistPagedTasksHalRepresentation destination, ResolutionContext context)
        {
            var tasks = mapper.Map<IList<TaskSummaryHalRepresentation>>(source.Tasks.ResultPage.ToList());
            var worklistPagedTasksHalRepresentation = new WorklistPagedTasksHalRepresentation(
                new PaginatedResult<TaskSummaryHalRepresentation>
                {
                    ResultPage = tasks.AsQueryable(),
                    TotalCount = source.Tasks.TotalCount,
                    PageNumber = source.Tasks.PageNumber
                },
                httpContextAccessor.HttpContext.Request)
            {
                Id = source.Id,
                OwnerId = source.OwnerId,
                OwnerName = source.OwnerName
            };
            return worklistPagedTasksHalRepresentation;
        }
    }

The Convert method is called by a controller method.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 47 (19 by maintainers)

Commits related to this issue

Most upvoted comments

@davidfowl / @muratg We 100% need this fix back ported, please!

Yes this is a change made in 2.2. It’s fixed in 3.0, maybe we should backport that’s change to 2.2.x.

cc @muratg

@muratg Thats great news, however we do are in a migration process and we did found a workaround after looking at how the “issued” HttpContextAcessor is implemeted, so we inject the HttpContextAcessor when we affect the TraceIdentifier and imediatly reset it so that it is available for the rest of the HttpRequest execution. We don’t see any side-effects and after preliminary tests, seems a suitable workaround while we don’t have 2.2.2 ready.

public class CorrelationIdMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IHttpContextAccessor _httpAccessor;

    public CorrelationIdMiddleware(RequestDelegate next, IHttpContextAccessor httpAccessor)
    {
        _next = next;
        _httpAccessor = httpAccessor;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Headers.ContainsKey("X-Correlation-ID"))
        {
            context.TraceIdentifier = context.Request.Headers["X-Correlation-ID"];
            // WORKAROUND: On ASP.NET Core 2.2.1 we need to re-store in AsyncLocal the new TraceId, HttpContext Pair
            _httpAccessor.HttpContext = context;
        }

        // Call the next delegate/middleware in the pipeline
        await _next(context);
    }
}

Hope this helps.

Best Regards, Fernando Nunes

As a simple workaround you can also create your own implementation of IHttpContextAccessor:

namespace MyComponents
{
    public class HttpContextAccessor : IHttpContextAccessor
    {
        private static AsyncLocal<HttpContext> _httpContextCurrent = new AsyncLocal<HttpContext>();

        public HttpContext HttpContext
        {
            get => _httpContextCurrent.Value;
            set => _httpContextCurrent.Value = value;
        }
    }
}

and register that instead:

//services.AddHttpContextAccessor();
services.TryAddSingleton<IHttpContextAccessor, MyComponents.HttpContextAccessor>();

No I’m on vacation, have somebody backport the PR 😬

@Tratcher I believe we have run into a similar issue with TestServer and I opened a new issue with a repro here: https://github.com/aspnet/AspNetCore/issues/7975

@JunTaoLuo Could you prepare this for 2.2.2 please?

Work is backporting Ben’s PR from master and filling out the shiproom template.

@davidfowl: I have made another web application project, even simpler, that can be used to reproduce the problem. I am 100% convinced that if you alter the HttpContext.TraceIdentifier from the middleware code you force HttpContextAccessor instance to set its HttpContext property to null.

Please see the attached sample application. WebApplication2.zip

If you call GET api/values without specifying the HTTP header X-Correlation-ID everything works fine. But if you make the following request:

GET http://localhost:60065/api/values HTTP/1.1
X-Correlation-ID: myCorrelationId
accept: application/json
Accept-Language: en-US,en;q=0.8,nb;q=0.6,ro-RO;q=0.4,ro;q=0.2
Host: localhost:60065

the HttpContext property of IHttpContextAccessor becomes null. This happens because in CurrentRequestMiddleware I’m setting the value of HttpContext.TraceIdentifier to the value of X-Correlation-ID header.

Thank you.

@davidfowl the constructor level injection doesn’t work and I believe the explanation is here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write?view=aspnetcore-2.2#per-request-dependencies

Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors aren’t shared with other dependency-injected types during each request. If you must share a scoped service between your middleware and other types, add these services to the Invoke method’s signature.

In my example, the injected IAppLoggingRepository depends on IAppIdentity, which as a Transient per-request scope.

Either should work

@kieronlanning Should be sometime in February.