locked
Create database context from cookie and base path RRS feed

  • Question

  • User-1262787652 posted

    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);
                }
            }
        }

    Monday, December 28, 2020 2:18 PM

All replies

  • User475983607 posted

    The problem is you are not following standard ASP.NET Core dependency injection fundamentals.  Services are injected through the constructor.  You are manually "newing" the SchemeManager which invokes the parameterless constructor.  

    I recommend reading the official dependency reference docs and maybe take a look at the C# programming guide to understand how constructors work.

    https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0

     

    Monday, December 28, 2020 2:39 PM
  • User753101303 posted

    Hi,

    Seems expected as you are using explicitely the SchemeManager constructor that doesn't provide the needed dependency.

    Don't you inject IEevaContextFactory in your controller that you could then pass to SchemeManager. A step further would be to register and inject SchemeManager as well.

    Dependency injection resolves the whole graph ie if you register a class A needed to create a class B needed to create a class C, when you'll inject class C, DI will walk through those dependencies and will create A, B from A and C from B to give you back what you asked for.

    Monday, December 28, 2020 2:52 PM
  • User-1262787652 posted

    Hi!

    In real application controller calls other method, other method calls other etc. Database access occurs only at at nth nesting level.

    Using your hint 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 ? 

    Monday, December 28, 2020 3:53 PM
  • User753101303 posted

    Seems to me you have all in place ie you could:

    1) delete the parameterless SchemeManager constructor so that you can't create a class you can't use anyway
    2) add  services.AddScoped<SchemeManager>(); to available services
    3) then rather than creating the SchemeManager yourself you can use

    private readonly SchemeManager _scheme;
    public HomeController(SchemeManager scheme)
    {
       _scheme = scheme;

    and so this class (and all on which it depends) is created for you

    Monday, December 28, 2020 5:57 PM
  • User-1262787652 posted

    Hi!

    It loks that dependecy injection and factory pattern do not have any additional value in this case.

    It looks like most reasonable is to pass controller HttpContext  property to every data-access method called from controller and not use DI and factory patterns?

    Or is it more reasonable to pass Cookie and PathBase values from HttpContext.Request as strings to avoid HttpContext become null in async methods ?

    Monday, December 28, 2020 6:33 PM
  • User753101303 posted

    Yes, if you pass or use the HttpContext directly then a current http request is required which can be a problem if you later want to write tests, have to create a console job or a dekstop admin app, allow an admin to select another tenant/schema than the "current" one  or just want to change your mind about which source within the HttpContext is used (and make sure it is changed everywhere it is needed).

    If instead you pass just the needed information as strings you'll avoid to take that dependency and will add the flexibility to eaésily pass whatever best fit.

    DI is a bit similar. If class B is used directly by multiple classes, this coupling could be changed only by doing mutliple code changes. If instead you are using DI a single change is enough to test a newer or another version etc... Keep this in mind to see over time if you don't find your own use cases where DI would have been easier.

    Edit: BTW your WebMatrix issue could be a quite good use case. It depends directly on the old configuration system while ASP.NET Core uses IConfiguration which allows to get the config from any source you want (even not yet existing). A bit late here but if starting from the very beginning, it seems you are doing anyway nothing else than changing the connection string based on the current http request? I would have to try but UsenpgSql might allow that already (I believe I have seen that for SQL Server).

    Monday, December 28, 2020 11:28 PM