2

I would like to implement singleton pattern in StudentProvider and then access method through interface. StudentProvider constructor accepts few parameters. Here's the sample working code without singleton.

public interface IStudentProvider
{
    Task<StudentViewModel> GetStudentAsync();
}

public class StudentProvider : IStudentProvider
{
    private readonly HttpContext httpContext;
    private readonly IActionContextAccessor actionContextAccessor;
    private readonly IConfiguration configuration;
    private readonly IUnitOfWork unitOfWork;
    private readonly string host;

    public StudentProvider(IHttpContextAccessor _httpContextAccessor, IActionContextAccessor _actionContextAccessor, IConfiguration _configuration, IUnitOfWork _unitOfWork)
    {
        httpContext = _httpContextAccessor.HttpContext;
        
        actionContextAccessor = _actionContextAccessor;
        configuration = _configuration;
        unitOfWork = _unitOfWork;
        host = _httpContextAccessor.HttpContext.Request.Host.Host;
    }

    public async Task<StudentViewModel> GetStudentAsync()
    {
        var std = new StudentViewModel();

        // httpContext, actionContextAccessor, configuration, unitOfWork and host uses here

        return std;
    }
}

Now i converted this into single, here's the code:

public interface IStudentProvider
{
    Task<StudentViewModel> GetStudentAsync();
}

public sealed class StudentProvider : IStudentProvider
{
    private readonly HttpContext httpContext;
    private readonly IActionContextAccessor actionContextAccessor;
    private readonly IConfiguration configuration;
    private readonly IUnitOfWork unitOfWork;
    private readonly string host;

    private static StudentProvider instance = null;

    public static StudentProvider GetInstance
    {
        get
        {
            if (instance == null)
            {
                instance = new StudentProvider();
            }

            return instance;
        }
    }

    private StudentProvider(IHttpContextAccessor _httpContextAccessor, IActionContextAccessor _actionContextAccessor, IConfiguration _configuration, IUnitOfWork _unitOfWork)
    {
        httpContext = _httpContextAccessor.HttpContext;

        actionContextAccessor = _actionContextAccessor;
        configuration = _configuration;
        unitOfWork = _unitOfWork;
        host = _httpContextAccessor.HttpContext.Request.Host.Host;
    }

    public async Task<StudentViewModel> GetStudentAsync()
    {
        var std = new StudentViewModel();

        // httpContext, actionContextAccessor, configuration, unitOfWork and host uses here

        return std;
    }
}

The issue with above singleton code is instance = new StudentProvider(); is expecting parameters which i'm not able to pass.

How do i pass parameters to constructor from singleton instance ?

Abhimanyu
  • 2,173
  • 2
  • 28
  • 44
  • 4
    You can't, basically. You don't have enough information - unless you can create all the required objects within that property. I would suggest this is a good time to abandon the singleton pattern (which hurts testability and leads to global state) and instead use dependency injection. – Jon Skeet Dec 15 '21 at 09:07

2 Answers2

0

It seems that you're using ASP.NET and it's dependency injection. If so, you can use AddSingleton to register your provider instead of implementing your own singleton pattern. Singleton.

BTW, your provider depends on a HttpContext which means you need to create different instance for different requests.

  • I'm already using AddSingleton but a single page load create 10 objects of StudentProvider. – Abhimanyu Dec 15 '21 at 09:33
  • Can you verify that again, because AddSingleton will create only 1 object of StudentProvider? – Vivek Khandelwal Dec 15 '21 at 09:41
  • my bad, it is currently services.AddTransient(); but as soon as i change it to services.AddSingleton(); its throwing error InvalidOperationException: Cannot consume scoped service 'DataContext' from singleton 'Extensions.IStudentProvider'. – Abhimanyu Dec 15 '21 at 09:54
  • 1
    See [this anwser](https://stackoverflow.com/a/45811225/6416061) – Xiaofeng Zheng Dec 16 '21 at 05:18
  • @XiaofengZheng let me try this, i would not use Singleton for sure. I see mix of AddScoped and AddTransient for the services we created. Also, going to use Scoped (earlier there was nothing, not sure what is the default Lifetime is its null, please if you know) with DbContext like services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("ConnString")), ServiceLifetime.Scoped); – Abhimanyu Dec 16 '21 at 08:27
  • `AddDbContext` use `Scoped` as its default lifetime: https://github.com/dotnet/efcore/blob/main/src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs#L57 – Xiaofeng Zheng Dec 16 '21 at 09:21
0

As @Jon Skeet suggested, it will be better to use Dependency Injection.

I will also recommend to @Xiaofeng Zheng solution to use the singleton dependency injection with factory pattern.

And if all these does not satisfy, you can go with below solution.

You will need to keep the reference of IServiceProvider as singleton in your Startup file which can be accessed globally.

public class Startup
{
  public static IServiceProvider ServiceProvider { get; private set; }

  public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider) {
    ...

    ServiceProvider = serviceProvider;
  }
}

Then, you can access the Startup.ServiceProvider within your StudentProvider to create the instance of other dependencies.

using Microsoft.Extensions.DependencyInjection;

public sealed class StudentProvider : IStudentProvider
{
    private readonly HttpContext httpContext;
    private readonly IActionContextAccessor actionContextAccessor;
    private readonly IConfiguration configuration;
    private readonly IUnitOfWork unitOfWork;
    private readonly string host;

    private static StudentProvider instance = null;

    public static StudentProvider GetInstance
    {
        get
        {
            if (instance == null)
            {
                instance = new StudentProvider(
                    Startup.ServiceProvider.GetService<IHttpContextAccessor>(), 
                    Startup.ServiceProvider.GetService<IActionContextAccessor>(), 
                    Startup.ServiceProvider.GetService<IConfiguration>(), 
                    Startup.ServiceProvider.GetService<IUnitOfWork>()
                );
            }

            return instance;
        }
    }

    private StudentProvider(IHttpContextAccessor _httpContextAccessor, IActionContextAccessor _actionContextAccessor, IConfiguration _configuration, IUnitOfWork _unitOfWork)
    
}
Vivek Khandelwal
  • 7,829
  • 3
  • 25
  • 40