0

Postgres database has multiple schemes like company1, company2, ... companyN

Browser sends cookie containing scheme name . Data access operations should occur in this scheme. Web application user can select different scheme. In this case different cookie value is set.

Npgsql EF Core Data provider is used.

ASP NET MVC 5 Core application registers factory in StartUp.cs :

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpContextAccessor();
        services.AddScoped<IEevaContextFactory, EevaContextFactory>();
      ....

Home controller tries to use it:

public class HomeController : EevaController
{
    public ActionResult Index()
    {
        var sm = new SchemeManager();
        sm.PerformInsert();
    ....

This throws exception since factory member is null. How to fix this ?

public interface IEevaContextFactory
{
    EevaContext Create();
}

public class EevaContextFactory : IEevaContextFactory
{
    private IHttpContextAccessor httpContextAccessor;
    private IConfiguration configuration;

    public EevaContextFactory(IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
    {
        this.httpContextAccessor = httpContextAccessor;
        this.configuration = configuration;
    }

    public EevaContext Create()
    {
        var builder = new DbContextOptionsBuilder<EevaContext>();
        var pathbase = httpContextAccessor.HttpContext.Request.PathBase.Value;
        var scheme = httpContextAccessor.HttpContext.Request.Cookies["Scheme"];

        var csb = new NpgsqlConnectionStringBuilder()
        {
            Host = pathbase,
            SearchPath = scheme
        };
        builder.UseNpgsql(csb.ConnectionString);
        return new EevaContext(builder.Options);
    }
}

Scheme data acess methods:

public class SchemeManager
{
    readonly IEevaContextFactory factory;

    public SchemeManager(IEevaContextFactory factory)
    {
        this.factory = factory;
    }

    public SchemeManager()
    {
    }

    public void PerformInsert()
    {
        using (var context = factory.Create())
        {
            var commandText = "INSERT into maksetin(maksetin) VALUES (CategoryName)";
            context.Database.ExecuteSqlRaw(commandText);
        }
    }
}
Andrus
  • 26,339
  • 60
  • 204
  • 378
  • The manager needs to be injected so that the DI container can inject the necessary dependencies. STOP trying to create these dependencies manually. That is why you keep getting nulls – Nkosi Dec 28 '20 at 16:46
  • You seriously need to rethink your design or you are just going to end up in more problems trying to maintain your code. – Nkosi Dec 28 '20 at 16:48
  • Constructors with dependency parameters are called only by .NET code. So it looks that most reasonable is to pass HttpContext (or only Cookie and pathbase values since HttpContext may be null on async operations?) from MVC Controller to every data access method or add those methods to controller base class. In this case HttpContext injection is not needed and usage of DI can avoided. – Andrus Dec 28 '20 at 17:14

1 Answers1

1
var sm = new SchemeManager()

... will call the no-parameter constructor on SchemeManager so the IEevaContextFactory is not injected. You should inject your factory into your controller and pass it into your SchemeManager.

Remove your no-parameter constructor. It's not needed.

public class HomeController : EevaController
{
    private IEevaContextFactor eevaFactory;
           
    public HomeController(IEevaContextFactory factory)
    {
         eevaFactory = factory;
    }
        
    public ActionResult Index()
    {
         var sm = new SchemeManager(eevaFactory);
         sm.PerformInsert();
         ....
    }
}

Your other option is to put the SchemeManager in the DI container and then the DI container will auto-resolve IEevaContextFactory on the constructor and then just inject SchemeManager into your controller.

Either way, remove that no-parameter constructor.

Neil W
  • 7,670
  • 3
  • 28
  • 41
  • In real application controller calls other method, other method calls other etc. Database access occurs only at at nth nesting level. Using this requires adding factory parameter to large number of methods. How to avoid this ? In .NET 4.8 ˇHttpContext.Currentˇ can used but it does not exist in .NET 5. How to use Dependenvy injection or other method for this ? – Andrus Dec 28 '20 at 15:49
  • @Andrus they removed `ˇHttpContext.Current` because it was a poor design that tightly coupled your code to static concerns that could not be mocked when testing. This made maintaining code difficult and was designed out of the new versions – Nkosi Dec 28 '20 at 16:52
  • @Andrus as mentioned, what you're looking for is an indicator that your architecture could do with a revamp. However, whilst I've not tested it, putting the ServiceProvider in a static variable during StartUp could hack you out of your situation. See Stefan Steiger's answer in the following ... https://stackoverflow.com/questions/38571032/how-to-get-httpcontext-current-in-asp-net-core – Neil W Dec 28 '20 at 16:56
  • I tried this and onter similar solutions with static property but static constructor is not called by .NET 5 for unknown reason. It looks like most reasonable is to pass controller HttpContextBase property to every data-access method called from controller and not use DI? Or is it more reasonable to extract Cookie and PathBase from HttpContext and pass only them to avoid HttpContext to become null in async methods ? – Andrus Dec 28 '20 at 17:20
  • 1
    Personally, in your situation, I'd not feed HttpContext down the chain. I'd inspect it as close to the surface as possible to create the object that's really needed down the chain and then pass that down instead. – Neil W Dec 28 '20 at 17:34
  • One final comment. I understand the frustration of being in your situation. Have been there myself and spent a long time burying my head in the sand with respect to using DI properly. But, from experience, I can assure you that once you've embraced it and begin configuring your whole application in Startup, you'll never wish for the old days again. – Neil W Dec 28 '20 at 17:38
  • So most reasonable is not to use DI and factory pattern in this case? Simply pass cookie and pathbase values as strings from Controller HttpBase property to all data-access methods called? – Andrus Dec 28 '20 at 18:29