Dynamic connection string to Web Api

asp.net-web-api connection-string entity-framework-6

Question

I am exposing my repository operations through web api. Repository has been implemented with Entity framework and Unit Of Work Pattern. I have many instances of the same database. Each one represent the data of a different Client. Now the issue is how can I set the connection string dynamically through each webapi call? Should I get connection string parameter with each call ? Or I should host web Api per client ?

1
7
5/22/2015 7:02:59 PM

Accepted Answer

Based on the information provided, I would use the same controller and look up the connection string rather than rather than hosting separate Web API instances for each client. There would be more complexity in hosting multiple instances and given the only difference indicated is the connection string, I do not think the complexity would be justified.

The first thing we will need to do is determine which client is calling in order to get the appropriate connection string. This could be done with tokens, headers, request data, or routing. Routing is simplest and most generally accessible to clients, so I will demonstrate using it; however, carefully consider your requirements in deciding how you will make the determination.

[Route( "{clientId}" )]
public Foo Get( string clientId ) { /* ... */ }

Next we need to get the right DbContext for the client. We want to keep using DI but that is complicated in that we do not know until after the Controller is created what connection string is needed to construct the object. Therefore, we need to inject some form of factory rather than the object itself. In this case we will represent this as a Func<string, IUnitOfWork> with the understanding it takes the 'clientId' as a string and returns an appropriately instantiated IUnitOfWork. We could alternatively use a named interface for this.

[RoutePrefix("foo")]
public class FooController : ApiController
{  
    private Func<string, IUnitOfWork> unitOfWorkFactory;

    public FooController( Func<string, IUnitOfWork> unitOfWorkFactory )
    {
        this.unitOfWorkFactory = unitOfWorkFactory;
    }

    [Route( "{clientId}" )]
    public Foo Get( string clientId )
    {
        var unitOfWork = unitOfWorkFactory(clientId);
        // ...
    }
}

All that remains is configuring our dependency injection container to provide us that Func<string, IUnitOfWork>. This could vary significantly between implementation. The following is one possible way to do it in Autofac.

protected override void Load( ContainerBuilder builder )
{
    // It is expected `MyDbContext` has a constructor that takes the connection string as a parameter
    // This registration may need to be tweaked depending on what other constructors you have.
    builder.Register<MyDbContext>().ForType<DbContext>().InstancePerRequest();

    // It is expected `UnitOfWork`'s constructor takes a `DbContext` as a parameter
    builder.RegisterType<UnitOfWork>().ForType<IUnitOfWork>().InstancePerRequest();

    builder.Register<Func<string, Bar>>(
        c =>
            {
                var dbContextFactory = c.Resolve<Func<string, DbContext>>();
                var unitOfWorkFactory = c.Resolve<Func<DbContext, IUnitOfWork>>();

                return clientId =>
                    {
                        // You may have injected another type to help with this
                        var connectionString = GetConnectionStringForClient(clientId);
                        return unitOfWorkFactory(dbContextFactory(connectionString));
                    };
            });
 }

Autofac is used since comments indicates Autofac is currently being used, though similar results would be possible with other containers.

With that the controller should be able to be instantiated and the appropriate connection string will be used for each request.


Example registration based on linked project:

builder.Register<Func<string, IEmployeeService>>(
    c =>
        {
            var dbContextFactory = c.Resolve<Func<string, IMainContext>>();
            var unitOfWorkFactory = c.Resolve<Func<IMainContext, IUnitOfWork>>();
            var repositoryFactory = c.Resolve<Func<IMainContext, IEmployeeRepository>>();
            var serviceFactory = c.Resolve<Func<IUnitOfWork, IEmployeeService>>();

            return clientId =>
                {
                    // You may have injected another type to help with this
                    var connectionString = GetConnectionStringForClient(clientId);

                    IMainContext dbContext = dbContextFactory(connectionString);
                    IUnitOfWork unitOfWork = unitOfWorkFactory(dbContext);
                    IEmployeeRepository employeeRepository = repositoryFactory(dbContext);
                    unitOfWork.employeeRepositoty = employeeRepository;

                    return serviceFactory(unitOfWork);
                };
        });

If you find the registration grows too cumbersome because of needing to do a little wiring manually, you probably need to look at updating (or creating a new) container after you have determined the client so that you can rely more on the container.

4
6/23/2015 7:06:58 PM

Popular Answer

You can change the connectionstring per DbContext instance

Example:

public class AwesomeContext : DbContext
{
    public AwesomeContext (string connectionString)
        : base(connectionString)
    {
    }
    public DbSet<AwesomePeople> AwesomePeoples { get; set; }
}

And then use your DbContext like this:

   using(AwesomeContext context = new AwesomeContext("newConnectionString"))
   {
     return context.AwesomePeoples.ToList();
   }

Depending on how many ConnectionStrings there are you can make a DB table for the client / constring mapping or save it in the solution (array for example).

If you can't/don't want to change the constructor you can do it later as well

Add this to your DbContext override:

    public void SetConnectionString(string connectionString)
    {
        this.Database.Connection.ConnectionString = connectionString;
    }

And call the method before you do any DB operations:

   using(AwesomeContext context = new AwesomeContext())
   {

    context.SetConnectionString(ConfigurationManager.ConnectionStrings["newConnectionString"].ConnectionString)
    return context.AwesomePeoples.ToList();

   }


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow