Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Singleton with AsyncLocal vs Scope Service

I looked at how HttpContext being created in .NET Core. Then I found that there is a class called HttpContextFactory which create and assign HttpContext object into a HttpContext property of HttpContextAccessor class. And to use HttpContext object in our code, we inject IHttpContextAccessor to the constructor of the class that needs the object.

When I looked at the implementation of HttpContextAccessor, apparently its HttpContext property gets the HttpContext object value from a private AsyncLocal variable and later on HttpContextAccessor is registered as Singleton.

https://github.com/aspnet/AspNetCore/blob/master/src/Http/Http/src/HttpContextAccessor.cs

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading;

namespace Microsoft.AspNetCore.Http
{
    public class HttpContextAccessor : IHttpContextAccessor
    {
        private static AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();

        public HttpContext HttpContext
        {
            get
            {
                return  _httpContextCurrent.Value?.Context;
            }
            set
            {
                var holder = _httpContextCurrent.Value;
                if (holder != null)
                {
                    // Clear current HttpContext trapped in the AsyncLocals, as its done.
                    holder.Context = null;
                }

                if (value != null)
                {
                    // Use an object indirection to hold the HttpContext in the AsyncLocal,
                    // so it can be cleared in all ExecutionContexts when its cleared.
                    _httpContextCurrent.Value = new HttpContextHolder { Context = value };
                }
            }
        }

        private class HttpContextHolder
        {
            public HttpContext Context;
        }
    }
}

I'm curious, what is the benefit of doing it this way instead of using a Scope service? It seems to me both would make the object available within a request scope.

If it's a scope service, I reckon the HttpContextAccessor will look something like this

using System.Threading;

namespace Microsoft.AspNetCore.Http
{
    public class HttpContextAccessor : IHttpContextAccessor
    {
        private HttpContextHolder _httpContextCurrent;

        public HttpContext HttpContext
        {
            get
            {
                return  _httpContextCurrent?.Context;
            }
            set
            {
                if (value != null)
                {
                    _httpContextCurrent = new HttpContextHolder { Context = value };
                }
            }
        }

        private class HttpContextHolder
        {
            public HttpContext Context;
        }
    }
}

Then use it as scope service

services.TryAddScope<IHttpContextAccessor, HttpContextAccessor>();

I would like to know what are the advantages and disadvantages for each approach, so that I'll understand when to use Singleton with AsyncLocal or Scope when creating a library for my project.

like image 801
muhihsan Avatar asked Oct 16 '22 13:10

muhihsan


2 Answers

As long as it is a singleton, the resolved IHttpContextAccessor instance could be holded permanently by a singleton service and work properly, while it could cause problems if a singleton service resolves a scoped IHttpContextAccessor.

like image 87
Alsein Avatar answered Nov 03 '22 22:11

Alsein


I guess one of the reasons could be that Asp.Net Core IServiceProvider does not allow Scoped Dependency to be injected inside Singleton class. That could be a major decision behind that. If things would have been scoped as you suggested then all the classes using it may have to be scoped. But interestingly enough once the request is served the HTTPContext becomes null.

like image 30
GPuri Avatar answered Nov 03 '22 22:11

GPuri