Ajay Suwalka
Ajay Suwalka

Reputation: 531

How to resolve runtime dependencies in castle windsor for C#

I have a specific scenario here where I need to pass the connection string based on the user, because users may be mapped to the different databases based on his/her enterprise.

This is the code I use to resolve the dependency with a static variable:

public void Install(IWindsorContainer container, IConfigurationStore store)
{
    container.Register(
        Component.For<IUserRepository>()
                 .ImplementedBy(typeof(IKS.Dare.Optimix.Repository.EntityFramework.UserModule.UserRepository))
                 .DependsOn(Dependency.OnValue("connectionString", DatabaseSettings.DefaultConnectionString))
    );
}

Because this DefaultConnectionString is supposed to be a dynamic one, I don't want to lock this variable to make it thread safe, as this would degrade the performance. I would want a way so that I can deal with such situation.

Possible consideration which can be that we can give a session, which can be applied as follows:

DynamicParameters((k, d) => d["connectionString"] = Session["connectionString"])

But this is in a different project which doesn't utilize any web component, it's just an installer project which is basically designed for resolving the dependencies only.

My Generic repository looks like following

public class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity
{
        private const string IsActive = "IsActive", DbContext = "dbContext", EntityPropertyName = "Entity";

        private string connectionString = String.Empty, provider = String.Empty;

        public GenericRepository(string connectionString, string provider)
        {
            this.connectionString = connectionString;
            this.provider = provider;
        }
        public int Count()
        {
            string tableName = typeof(T).Name;
            string query = SqlQueryConstants.SelectCount + SqlQueryConstants.Space + tableName;
            int count = DbHelper.ExecuteScalar<int>(query: query, commandType: System.Data.CommandType.Text, connectionString: connectionString, provider: provider, parameters: null);
            return count;
        }
}

DBHelper class looks like follows

public static int ExecuteNonQuery(string query, CommandType commandType = CommandType.StoredProcedure,
            IList<DbParameter> parameters = null, int? timeout = null, string connectionString = "", string provider = "")
        {
            using (var connection = CreateDbConnection(connectionString, provider))
            {
                connection.Open();
                using (DbCommand command = CreateDbCommand(sqlQuery: query, parameters: parameters,
                                                connection: connection, commandType: commandType, timeout: timeout))
                {
                    return command.ExecuteNonQuery();
                }
            }
        }

        public static DbParameter CreateParameter<TValue>(string name, TValue value, DbType dbType,
            ParameterDirection parameterDirection = ParameterDirection.Input, string provider = "")
        {
            DbParameter param = CreateDbProviderFactory(provider).CreateParameter();
            param.Value = value;
            param.ParameterName = name;
            param.DbType = dbType;
            param.Direction = parameterDirection;
            return param;
        }


        public static DbConnection CreateDbConnection()
        {
            return CreateDbConnection(String.Empty, String.Empty);
        }

        public static DbConnection CreateDbConnection(string connectionString = "", string provider = "")
        {
            DbConnection connection = null;
            if (String.IsNullOrEmpty(provider))
            {
                if (String.IsNullOrEmpty(DatabaseSettings.DefaultProvider))
                    throw new ArgumentNullException("provider");
                else
                    provider = DatabaseSettings.DefaultProvider;
            }
            connection = CreateDbProviderFactory(provider).CreateConnection();
            connection.ConnectionString = connectionString;
            return connection;
        }

Any help would be greatly appreciated.

Note : I couldn't edit steven's answer. [EDIT] To make it more clear it can be implemented as:

Here controller is inherited from BaseController

public class UserController : BaseController
    {
        //
        // GET: /Index/
        private IUserRepository userRepository;

        public UserController(IUserRepository userRepository)
            : base(userRepository)
        {
            this.userRepository = userRepository;
        }
}

and BaseController is inherited from Controller where in the database settings are being set in the constructor of Base controller so that we don't need to set it everywhere

public abstract class BaseController : Controller
    {
        public BaseController(IUserRepository userRepository)
        {
            userRepository.connectionStringProvider.Provider = WebUtilities.CurrentUserData.Provider;
            userRepository.connectionStringProvider.ConnectionString = WebUtilities.CurrentUserData.ConnectionString;
        }
    }

Upvotes: 1

Views: 1626

Answers (2)

Steven
Steven

Reputation: 172646

Since, the connection string is runtime data, you should not use it to construct your application components, as is described in this article. So as the article advices, you should hide the connection string behind a provider abstraction. For instance:

public interface IConnectionStringProvider {
    string ConnectionString { get; }
}

This way your repositories can depend on IConnectionStringProvider and can call IConnectionStringProvider.ConnectionString at runtime:

public int Count()
{
    string tableName = typeof(T).Name;
    string query = SqlQueryConstants.SelectCount + SqlQueryConstants.Space + tableName;
    return DbHelper.ExecuteScalar<int>(
        this.connectionStringProvider.ConnectionString, 
        provider: provider, parameters: null);
}

It will be trivial to create an IConnectionStringProvider to will get the correct connection string for you:

class DatabaseConnectionStringProvider : IConnectionStringProvider
{
    public string ConnectionString => Session["connectionString"];
}

Since this clas depends on application-specifics (the ASP.NET session in this case), the class should not be part of the application's core logic. Instead, this adapter should live in the application's start up path (a.k.a. the composition root, the place where you configure your container).

You might even want to consider not passing along the IConnectionStringProvider into your repositories, but instead create an abstraction that will create a connection itself. This will hide the fact that there is a connection string completely.

Upvotes: 3

PatrickSteele
PatrickSteele

Reputation: 14677

What you're looking for is multi tenancy. You can google "castle windsor multi tenancy" and find a number of useful articles.

Here's a similar Stackoverflow question that links to some good articles on Windsor and multi tenancy. In particular, look into Windsor's IHandlerSelector interface.

Upvotes: 0

Related Questions