Search code examples
asp.net-mvclight-inject

Web API, Light Inject and Passing a Static Dictionary to the data layer


We have a multi-database solution and are passing the connection string to a factory function like so:

container.Register<IDbContextFactory>(
    f => new DynamicDbContextFactory(ClientConfig.GetConnectionString()),
    new PerScopeLifetime());

ClientConfig contains a static dictionary that gets populated on app start that maps a sub domain to a connection string. It seems that this approach is causing a memory leak (not 100% sure about this causing the leak but there is a leak).

public class ClientConfig
{
    private static ConcurrentDictionary<string, string> ConnectionStringManager 
    { 
        get;
        set; 
    }

    // etc.
}

My question is in MVC what is the best way to hold a list of connection strings that can be easily looked up on each request in order to pass that down the chain.


Solution

  • Edit : The question was initially tagged with Autofac


    With Autofac you don't have to use a dictionary and something like that to do what you want. You can use a custom parameter :

    public class ConnectionStringParameter : Parameter
    {
        public override Boolean CanSupplyValue(ParameterInfo pi, 
                                               IComponentContext context, 
                                               out Func<Object> valueProvider)
        {
            valueProvider = null;
    
            if (pi.ParameterType == typeof(String)
                && String.Equals(pi.Name, "connectionString", 
                                 StringComparison.OrdinalIgnoreCase))
            {
                valueProvider = () =>
                {
                    // get connectionstring based on HttpContext.Current.Request.Url.Host 
                    return String.Empty;
                };
            }
    
            return valueProvider != null;
        }
    }
    

    Then register your Parameter using a Module

    public class ConnectionStringModule : Autofac.Module
    {
        protected override void AttachToComponentRegistration(
            IComponentRegistry componentRegistry, IComponentRegistration registration)
        {
            registration.Preparing += registration_Preparing;
        }
    
        private void registration_Preparing(Object sender, PreparingEventArgs e)
        {
            Parameter[] parameters = new Parameter[] { new ConnectionStringParameter() }; 
            e.Parameters = e.Parameters.Concat(parameters);
        }
    }
    

    Module you have to register inside your container using

    ContainerBuilder builder = new ContainerBuilder();
    builder.RegisterModule(new ConnectionStringModule());
    

    Each time Autofac have to resolve a parameter of type String named connectionString it will used the custom parameter and get your connectionstring based on what you want.


    By the way this code sample use HttpContext.Current. In case of a multithreaded process it may return null. I don't recommend using HttpContext.Current for such things. You can use an intermediate class instead of accessing it, for example a IConnectionstringProvider interface.

    public interface IConnectionstringProvider
    {
        String ConnectionString { get; }
    }
    public class ConnectionStringProvider : IConnectionstringProvider
    {
        public ConnectionStringProvider(Strong host)
        {
            // get connectionstring based on host
            this._connectionString = String.Empty;
        }
    
        private readonly String _connectionString;
    
        public String ConnectionString
        {
            get { return this._connectionString; }
        }
    }
    

    Inside your Parameter you will have to change the valueProvider by

    valueProvider = () =>
    {
        return context.Resolve<IConnectionstringProvider>().ConnectionString; 
    };
    

    And finally you will have to register your IConnectionstringProvider at the beginning of the request lifetimescope :

    class Program
    {
        static void Main(string[] args)
        {
            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterModule(new ConnectionStringModule());
    
            IContainer container = builder.Build();
            container.ChildLifetimeScopeBeginning += container_ChildLifetimeScopeBeginning;
        }
    
        private static void container_ChildLifetimeScopeBeginning(
            Object sender, LifetimeScopeBeginningEventArgs e)
        {
            String host = HttpContext.Current.Request.Url.Host; 
            ContainerBuilder childLifetimeScopeBuilder = new ContainerBuilder();
            childLifetimeScopeBuilder.RegisterInstance(new ConnectionStringProvider(host))
                                     .As<IConnectionstringProvider>()
                                     .SingleInstance();
            childLifetimeScopeBuilder.Update(e.LifetimeScope.ComponentRegistry);
        }
    }
    

    Of course there is many way to do it but you have the idea