【问题标题】:Change injected object at runtime在运行时更改注入的对象
【发布时间】:2015-06-06 16:30:43
【问题描述】:

我想要 IUserRepository 的多个实现,每个实现都可以使用 MongoDB 或任何 SQL 数据库的数据库类型。为此,我拥有具有连接字符串和其他租户配置的 ITenant 接口。租户被注入到 MongoDB 或任何 SQLDB 实现的 IUserRepository 中。我需要知道的是如何正确更改注入的存储库以根据租户选择数据库。

接口

public interface IUserRepository 
{
    string Login(string username, string password);
    string Logoff(Guid id);
}

public class User
{
    public Guid Id { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }

}

public interface ITenant
{
    string CompanyName { get; }
    string ConnectionString { get; }
    string DataBaseName { get; }
    string EncriptionKey { get; }

}

重要的是要知道租户 ID 已通过标头请求传递给 API

StartUp.cs

// set inject httpcontet to the tenant implemantion
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();

// inject tenant
services.AddTransient<ITenant, Tenant>();

// inject mongo repository but I want this to be programmatically
services.AddTransient<IUserRepository, UserMongoRepository>();

Mongo 实施示例

public class UserMongoRepository : IUserRepository
{

    protected ITenant Tenant 

    public UserMongoRepository(ITenant tenant) :
        base(tenant)
    {
        this.Tenant = tenant;
    }

    public string Login(string username, string password)
    {

        var query = new QueryBuilder<User>().Where(x => x.Username == username);
        var client = new MongoClient(this.Tenant.ConnectionString);var server = client.GetServer();
        var database =  client.GetServer().GetDatabase(this.Tenant.DataBaseName);
        var user = database.GetCollection<User>.FindAs<User>(query).AsQueryable().FirstOrDefault();

        if (user == null)
            throw new Exception("invalid username or password");

        if (user.Password != password)
            throw new Exception("invalid username or password");

         return "Sample Token";

    }

    public string Logoff(Guid id)
    {

        throw new NotImplementedException();
    }

}

租户

public class Tenant : ITenant
{

    protected IHttpContextAccessor Accesor;
    protected IConfiguration Configuration;

    public Tenant(IHttpContextAccessor accesor, IDBConfiguration config)
    {
        this.Accesor = accesor;
        this.Configuration = new Configuration().AddEnvironmentVariables();
        if (!config.IsConfigure)
            config.ConfigureDataBase();
    }


    private string _CompanyName;
    public string CompanyName
    {
        get
        {
            if (string.IsNullOrWhiteSpace(_CompanyName))
            {
                _CompanyName = this.Accesor.Value.Request.Headers["Company"];
                if (string.IsNullOrWhiteSpace(_CompanyName))
                    throw new Exception("Invalid Company");
            }
            return _CompanyName;
        }
    }

    private string _ConnectionString;
    public string ConnectionString
    {
        get
        {
            if (string.IsNullOrWhiteSpace(_ConnectionString))
            {
                _ConnectionString = this.Configuration.Get(this.CompanyName + "_" + "ConnectionString");
                if (string.IsNullOrWhiteSpace(_ConnectionString))
                    throw new Exception("Invalid ConnectionString Setup");
            }
            return _ConnectionString;
        }
    }

    private string _EncriptionKey;
    public string EncriptionKey
    {
        get
        {
            if (string.IsNullOrWhiteSpace(_EncriptionKey))
            {
                _EncriptionKey = this.Configuration.Get(this.CompanyName + "_" + "EncriptionKey");
                if (string.IsNullOrWhiteSpace(_EncriptionKey))
                    throw new Exception("Invalid Company Setup");
            }
            return _EncriptionKey;
        }
    }

    private string _DataBaseName;
    public string DataBaseName
    {
        get
        {
            if (string.IsNullOrWhiteSpace(_DataBaseName))
            {
                _DataBaseName = this.Configuration.Get(this.CompanyName + "_" + "DataBaseName");
                if (string.IsNullOrWhiteSpace(_DataBaseName))
                    throw new Exception("Invalid Company Setup");
            }
            return _DataBaseName;
        }
    }
}

控制器

public class UsersController : Controller
{
    protected IUserRepository DataService;

    public UsersController(IUserRepository dataService)
    {
        this.DataService = dataService;
    }

    // the controller implematation

}

【问题讨论】:

    标签: asp.net-core asp.net-core-mvc


    【解决方案1】:

    您应该为IUserRepository 定义一个代理实现并将实际实现隐藏在此代理后面,并在运行时决定将调用转发到哪个存储库。例如:

    public class UserRepositoryDispatcher : IUserRepository
    {
        private readonly Func<bool> selector;
        private readonly IUserRepository trueRepository;
        private readonly IUserRepository falseRepository;
    
        public UserRepositoryDispatcher(Func<bool> selector,
            IUserRepository trueRepository, IUserRepository falseRepository) {
            this.selector = selector;
            this.trueRepository = trueRepository;
            this.falseRepository = falseRepository;
        }
    
        public string Login(string username, string password) {
            return this.CurrentRepository.Login(username, password);
        }
    
        public string Logoff(Guid id) {
            return this.CurrentRepository.Logoff(id);
        }
    
        private IRepository CurrentRepository {
            get { return selector() ? this.trueRepository : this.falseRepository;
        }
    }
    

    使用这个代理类,您可以轻松地创建一个运行时谓词来决定使用哪个存储库。例如:

    services.AddTransient<IUserRepository>(c =>
        new UserRepositoryDispatcher(
            () => c.GetRequiredService<ITenant>().DataBaseName.Contains("Mongo"),
            trueRepository: c.GetRequiredService<UserMongoRepository>()
            falseRepository: c.GetRequiredService<UserSqlRepository>()));
    

    【讨论】:

    • 感谢@steve 有道理的回复,我可以看到使用调度程序的优势,我唯一注意到的是构造函数会变得非常大,因为我计划支持倍数数据库类型,我可以将租户传递给调度程序并在调度程序中进行选择
    • 嗨@Steven,你为什么不直接创建一个 UserMongoRepository 类的实例,比如“trueRepository: new UserMongoRepository()”。是不是因为这个类之前可能注入了一些东西?
    • @BarbarosAlp:我想你回答了你自己的问题。 UserMongoRepository 可能也需要由容器构建,因为它有自己的依赖项。
    • @Steven:感谢您的回答。如果你不介意我想再问你一个问题。我们不应该使用“c.GetRequiredService()”而不是“c.GetRequiredService()”。我很困惑,因为我总是在寻找 .
    • @BarbarosAlp:在原始问题的上下文中,拥有IUserMongoRepository 是没有用的,因为UserMongoRepository 只是IUserRepository 的实现。
    【解决方案2】:

    您可以尝试注入工厂而不是实际的存储库。工厂将负责根据当前用户身份构建正确的存储库。

    它可能需要更多样板代码,但它可以实现您想要的。一点点继承甚至可能使控制器代码更简单。

    【讨论】:

    • 但是如果你这样做,你会在控制器中注入什么对象?
    • 一个IUserRepositoryFactory 有一个方法可以解析当前租户的仓库
    猜你喜欢
    • 2016-09-28
    • 2012-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多