How to resolve runtime dependencies in castle windsor for C#

若如初见. 提交于 2019-12-11 13:56:21

问题


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

回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/37070532/how-to-resolve-runtime-dependencies-in-castle-windsor-for-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!