Изменить введенный объект во время выполнения

Я хочу иметь множественную реализацию IUserRepository, каждая реализация будет работать с типом базы данных MongoDB или любой базой данных SQL. Для этого у меня есть интерфейс ITenant со строкой подключения и другой конфигурацией арендатора. Арендатор внедряется в IUserRepository либо в MongoDB, либо в любую реализацию SQLDB. Что мне нужно знать, так это то, как правильно изменить внедренный репозиторий, чтобы выбрать базу данных на арендаторе.

Интерфейсы

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

}

Важно знать, что идентификатор арендатора был передан 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

}

person Son_of_Sam    schedule 18.03.2015    source источник


Ответы (2)


Вы можете попробовать внедрить фабрику, а не реальный репозиторий. Фабрика будет нести ответственность за создание правильного репозитория на основе текущего идентификатора пользователя.

Для этого может потребоваться немного больше шаблонного кода, но он может достичь того, чего вы хотите. Немного наследования может даже упростить код контроллера.

person Victor Hurdugaci    schedule 18.03.2015
comment
Но если вы это сделаете, какой объект вы будете внедрять в контроллер? - person Son_of_Sam; 18.03.2015
comment
IUserRepositoryFactory, у которого есть метод, который может разрешить репозиторий текущего клиента. - person Victor Hurdugaci; 18.03.2015

Вы должны определить реализацию прокси для 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>()));
person Steven    schedule 06.06.2015
comment
Спасибо за ответ, @steve имеет смысл, я вижу преимущество использования диспетчера. Единственное, что я замечаю, это то, что конструктор станет действительно большим, потому что я планирую поддерживать несколько типов БД, могу ли я передать арендатору диспетчер и сделать выбор в диспетчере - person Son_of_Sam; 09.06.2015
comment
привет @Steven, почему бы тебе напрямую не создать экземпляр класса UserMongoRepository, например trueRepository: new UserMongoRepository(). Это потому, что раньше в этот класс могло быть что-то введено? - person Barbaros Alp; 28.04.2016
comment
@BarbarosAlp: я думаю, вы сами ответили на свой вопрос. Контейнеру также может потребоваться создать UserMongoRepository, потому что у него есть собственные зависимости. - person Steven; 28.04.2016
comment
@Стивен: Спасибо за ответ. Если вы не возражаете, я хочу задать вам еще один вопрос. Разве мы не должны использовать c.GetRequiredService‹IUserMongoRepository›() вместо c.GetRequiredService‹UserMongoRepository›(). Я в замешательстве, потому что я всегда ищу «Интерфейс, Бетон». - person Barbaros Alp; 28.04.2016
comment
@BarbarosAlp: В контексте исходного вопроса было бы бесполезно иметь IUserMongoRepository, поскольку UserMongoRepository — это просто реализация IUserRepository. - person Steven; 28.04.2016
comment
@Стивен Большое спасибо! - person Barbaros Alp; 28.04.2016