Променете инжектирания обект по време на изпълнение

Искам да имам множествена реализация на 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 има смисъл, виждам предимството от използването на диспечера. Единственото нещо, което забелязвам е, че конструкторът ще стане наистина голям, защото планирам да поддържам множествен тип DB, мога ли да предам клиента на диспечера и направете избор в диспечера - 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
@Steven: Благодаря за отговора. Ако нямате нищо против, искам да ви задам още един въпрос. Не трябва ли да използваме c.GetRequiredService‹IUserMongoRepository›() вместо c.GetRequiredService‹UserMongoRepository›(). Объркан съм, защото винаги търся „Интерфейс, бетон“. - person Barbaros Alp; 28.04.2016
comment
@BarbarosAlp: В контекста на първоначалния въпрос би било безполезно да има IUserMongoRepository, тъй като UserMongoRepository е просто имплементация на IUserRepository. - person Steven; 28.04.2016
comment
@Steven Благодаря ви много! - person Barbaros Alp; 28.04.2016