Search code examples
castle-windsor

Castle windsor: how to pass arguments to deep dependencies?


I have the following dependency chain:

IUserAppService
IUserDomainService 
IUserRepository
IUserDataContext - UserDataContextImpl(string conn) 

All interfaces above and implementations are registered in a Windsor Castle container. When I use one connection string, everything works fine.

Now we want to support multiple databases, In UserAppServiceImpl.cs, we want to get different IUserRepository (different IUserDatabaseContext) according to userId as below:

// UserAppServiceImpl.cs
public UserInfo GetUserInfo(long userId)
{
   var connStr = userId % 2 == 0 ? "conn1" : "conn2";
   //var repo = container.Resolve<IUserRepository>(....)
}

How can I pass the argument connStr to UserDataContextImpl?


Solution

  • Since the connection string is runtime data in your case, it should not be injected directly into the constructor of your components, as explained here. Since however the connection string is contextual data, it would be awkward to pass it along all public methods in your object graph.

    Instead, you should hide it behind an abstraction that allows you to retrieve the proper value for the current request. For instance:

    public interface ISqlConnectionFactory
    {
        SqlConnection Open();
    }
    

    An implementation of the ISqlConnectionFactory itself could depend on a dependency that allows retrieving the current user id:

    public interface IUserContext
    {
        int UserId { get; }
    }
    

    Such connection factory might therefore look like this:

    public class SqlConnectionFactory : ISqlConnectionFactory
    {
        private readonly IUserContext userContext;
        private readonly string con1;
        private readonly string con2;
    
        public SqlConnectionFactory(IUserContext userContext,
            string con1, string con2) {
            ...
        }
    
        public SqlConnection Open() {
            var connStr = userContext.UserId % 2 == 0 ? "conn1" : "conn2";            
            var con = new SqlConnection(connStr);
            con.Open();
            return con;
        }
    }
    

    This leaves us with an IUserContext implementation. Such implementation will depend on the type of application we are building. For ASP.NET it might look like this:

    public class AspNetUserContext : IUserContext
    {
        public string UserId => int.Parse(HttpContext.Current.Session["UserId"]); 
    }