Essa dica vai mostrar para vocês como resolver as dependências do Unity (ou outro injetor de dependência que você usar) baseado em fatores dinâmicos do sistema, como por exemplo um determinado atributo de configuração, ou informação cadastral (baseado em variáveis cadastradas durante execução) sem ter assim que mudar o fonte ou XML’s.

Isto já me foi útil por diversas vezes durante as customizações de software, e é muito simples de ser feito.

Para que funcione, é necessário que exista uma característica que identifique a situação a ser customizada. E para o exemplo iremos simular um produto WEB em que várias empresas usam o mesmo sistema, sendo diferenciadas no momento do acesso – ex.: emp1.produto.com, emp2.produto.com, etc… -, tendo uma propriedade do objeto empresa que se chama CustomizationType – que é um enum – que possui a informação desta característica e pode ser alterada na tela de cadastro do sistema.

Esta propriedade representa uma característica que pode representar por exemplo o ramo de atividade da empresa, na qual há uma caracteristica diferente para calculo de imposto ou estoque.

Base

Nós armazenaremos a empresa ativa no sistema em um objeto de contexto chamado AppContext:

    public class AppContext : IAppContext
    {
        //O contexto é Singleton
        private static object _objSync = new object(); //lock para não haver problema por causa das threads
        private static volatile AppContext _instance;

        public static AppContext CurrentContext
        {
            get
            {
                if (_instance == null)
                {
                    lock (_objSync)
                    {
                        if (_instance == null)
                            _instance = new AppContext();
                    }
                }
                return _instance;
            }
        }

        #region Public Methods

        public Company CurrentCompany
        {
            get
            {
                return GetContext().CurrentCompany;
            }
            set
            {
                GetContext().CurrentCompany = value;
            }
        }

        #endregion

        #region Private Methods

        internal IAppContext GetContext()
        {
            return IoC.IoC.ResolveDefault<IAppContext>();
        }

        #endregion

    }
}

Nossa empresa ativa tem uma propriedade que se chama CustomizationType

 public enum SystemCustomizationType
    {
        /// <summary>
        /// Acesso padrão
        /// </summary>
        Defualt,

        /// <summary>
        /// Customização específica para o cliente A
        /// </summary>
        CustomerA, 

        /// <summary>
        /// Customização específica para todas as empresas do 3º setor
        /// </summary>
        ThirdSectorCompanies
    }

 

    public class Company
    {
        public virtual string Name { get; set; }
        public virtual string ContactName { get; set; }
        public virtual string PhoneNumber { get; set; }
        public virtual bool Active { get; set; }
        public virtual SystemCustomizationType CustomizationType { get; set; }
    }

Modificando o método no resolver

Tendo isto, vamos modificar o Resolver de nossa classe IoC (abaixo) para que carregue dinamicamente o objeto a ser retornado:

public static T Resolve<T>()
        {
            object obj = null;

            if (typeof(T).FullName != typeof(IAppContext).FullName && AppContext.CurrentContext.CurrentCompany != null)
            {
                try
                {
                    var newTypeName = typeof(T).FullName + AppContext.CurrentContext.CurrentCompany.CustomizationType;

                    Type newType = Type.GetType(newTypeName);

                    if (newType != null)
                        obj = _resolver.GetType().GetMethod("Resolve").MakeGenericMethod(newType).
                               Invoke(_resolver, null);
                }
                catch
                {
                    obj = null;
                }
            }

            if (obj == null)
                obj = _resolver.Resolve<T>();

            return (T)obj;
        }

Funcionamento do IoC neste caso

Quando o método resolve for chamado, a primeira coisa que ele deve fazer é buscar o CustomizationType da empresa que está dentro do AppContext

De posse da característica, o resolver irá tentar carregar um tipo que é o resultado da concatenação do nome do tipo solicitado com o CustomizationType do contexto:

var newTypeName = typeof(T).FullName +

                    AuthContext.CurrentContext.CurrentUser.Company.Parameters.AccessType;
 Type newType = Type.GetType(newTypeName);

Caso o newType seja validado com sucesso – caso ele encontre um tipo com o nome que foi gerado – ele usa reflection para criar uma instancia desse objeto, mas caso ele seja um objeto nulo, o resolver resolve o objeto baseado no próprio tipo enviado no parâmetro (<T>), que é o “tipo padrão”.

Exemplo:

Se estivermos logados com a empresa “Empresa A” que possui uma customização no serviço de usuários (CustomizationType = “CustomerA”), o método resolve irá procurar por uma classe chamada UserServiceCustomerA.

Quando estivermos logados com a “Empresa B” que possui customização nenhuma – que possui customizationType = null – o sistema irá retornar um objeto do tipo “UserService” que é o serviço de usuário que possui o comportamento padrão do sistema.

Fechando

Fazendo desta forma, minhas customizações são feitas sobrescrevendo métodos em uma nova classe, assim, quando customizo algo para o cliente ABC, eu não altero o fonte padrão que meus outros 300 clientes usam. Caso queiram ter uma percepção melhor de como faço isso  deem uma olhada neste post – antigo e que precisa muito de edição, mas da pra ter uma idéia – que fiz no Pangea no inicio do ano e que mostra melhor como o projeto foi organizado para esta situação.

Espero que a dica ajude alguém. Enjoy!