Primeiras aulas do curso C# Reflection parte 2: Implementando injeção de dependência do zero

C# Reflection parte 2: Implementando injeção de dependência do zero

Usando modelos em nossa View - Introdução

Olá pessoal da Alura, tudo bom? Meu nome é Guilherme e essa é a segunda parte do nosso curso de Reflection, na qual avançaremos bastante nesse recurso da linguagem. Por isso, recomendamos fortemente que você faça a primeira parte, caso ainda não tenha feito, até porque iremos continuar nosso projeto de criação de um portal para o banco ByteBank.

Trabalharemos no código de infraestrutura desse projeto, passando por diversos conceitos, como a chamada de construtores de um tipo, como visitar propriedades de uma classe dinamicamente e a feitura de um framework interessante e complexo para a inversão de controle. Se você não sabe o que é inversão de controle, não se preocupe, pois aprenderemos e implementaremos isso durante o curso.

Na construção dessa inversão de controle, verificaremos a hierarquia entre tipos, descobrindo dinamicamente se um tipo implementa uma interface ou se é filho de outra classe, construindo nosso contâiner de dependência, e criaremos tipos dinâmicos independentes de seus argumentos no construtor.

Também aprenderemos a visitar atributos de um método, e conseguiremos adornar nossos controlllers com vários atributos que acrescentarão significados na nossa aplicação web. Criaremos, por exemplo, um atributo que permitirá que uma URL da nossa página seja acessada apenas em horário comercial. Antes de executarmos o método, conseguiremos verificar esse atributo e executar uma lógica que permite ou não o acesso a uma página.

Como você pode ver, é um curso bem completo. Vamos começar?

Usando modelos em nossa View - Usando modelos em nossa View

Nossa aplicação já está ficando bem robusta: conseguimos montar URLs dinamicamente, resolver automaticamente URLs com um ou mais argumentos, encontrar sobrecargas, invocar, etc.

Porém, voltando ao nosso CambioController.cs, não dá para continuarmos dando Replace() em strings, o que parece mais uma "gambiarra". Uma prática muito comum que pode nos ajudar a melhorar esse processo é criarmos um objeto que representa o nosso modelo. Então, poderemos enviar esse modelo para a página HTML, na qual os valores serão colocados e substituídos corretamente.

Criaremos um modelo para representar câmbio de uma moeda/valor origem para uma moeda/valor destino. No BiteBankPortal, adicionaremos um novo diretório "Model" que armazenará nossos arquivos de modelo. Aqui, criaremos uma classe ública CalculoCambioModel terá quatro propriedades: uma string MoedaOrigem, uma string MoedaDestino, um decimal ValorOrigem e um decimal ValorDestino.

namespace ByteBank.Portal.Model
{
    class CalculoCambioModel
    {
        public string MoedaOrigem { get; set; }
        public string MoedaDestino { get; set; }

        public decimal ValorOrigem { get; set; }
        public decimal ValorDestino { get; set; }

    }

}

Para mantermos um modelo mais simples, não iremos alterar a visibilidade desses setters, colocá-los privados e passá-los no construtor. No nosso CambioController, ao invés dos vários Replace(), usaremos o nosso modelo, sem nos esquecermos da diretiva de using.

var modelo = new CalculoCambioModel();

Ao invés dos parênteses, podemos utilizar o auto initializer, uma sintaxe do C# representada por {}, para já popularmos os argumentos. Entre as chaves, passaremos as propriedades que queremos setar: MoedaDestino (a moedaDestino sendo recebida por parâmetro), ValorDestino (o valorFinal retornado pelo nosso serviço), moedaOrigem (a moedaOrigem do nosso parâmetro) e ValorOrigem (o valor ue recebemos na nossa action).

public string Calculo(string moedaOrigem, string moedaDestino, decimal valor)
{
    var valorFinal = _cambioService.Calcular(moedaOrigem, moedaDestino, valor);
    var textoPagina = View();

    var modelo = new CalculoCambioModel
        {
            moedaDestino = moedaDestino,
            ValorDestino = valorFinal,
            moedaOrigem = moedaOrigem,
            ValorOrigem = valor
        };

Já temos uma sobrecarga que retorna um HTML, e seria interessante se tivéssemos mais uma que recebesse por argumento um modelo e o aplicasse no nosso HTML. Então, no ControllerBase, criaremos um novo método protegido que retornará uma string e se chamará View(). Esse método será uma sobrecarga que, como primeiro parâmetro, receberá um object que será o modelo da nossa página. Também usaremos o nome do arquivo (nomeArquivo), portanto usaremos o recurso CallerMemberName do C# para preenchermos essa variável com o nome de quem chamou esse método.

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{

}

Dentro dessa sobrecarga que tem o modelo, vamos simplesmente chamar o outro método View(). Portanto, criaremos uma variável viewBruta (que não recebeu nenhum processamento ou preparação) que receberá a chamada do View() passando adiante o nomeArquivo.

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);

}

A partir daqui, precisamos tratar essa variável de acordo com nosso modelo. No CambioController, colocaremos a variável textoPagina abaixo da criação do nosso modelo. Dessa forma, poderemos passar adiante a variável modelo. Feito isso, poderemos apagar o código contendo os Replace(), afinal a sobrecarga resolverá isso. Inclusive, ao invés de criarmos a variável textoPagina, podemos simplesmente retornar o valor da chamada de View(modelo)

public string Calculo(string moedaOrigem, string moedaDestino, decimal valor)
{
    var valorFinal = _cambioService.Calcular(moedaOrigem, moedaDestino, valor);
    var modelo = new CalculoCambioModel
        {
            MoedaDestino = moedaDestino,
            ValorDestino = valorFinal,
            moedaOrigem = moedaOrigem,
            ValorOrigem = valor
        };

    return View(modelo);
}

Ainda falta programarmos, mas dessa forma nosos método terá uma forma muito mais robusta e elegante. Se nós estamos alterando a forma que a nossa vire é preenchida, precisaremos remover as strings mágicas do nosso Calculo.html. Também podemos criar outra convenção para indicar o nome da propriedade ou modelo que deve ser injetado nesse código HTML. Portanto, ao invés dessas strings coringas, usaremos o nome das propriedades.

<!DOCTYPE html>
<html>
<header>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">        
    <title>ByteBank</title>
</header>
<body>
    <h1>ByteBank!</h1>
    <h2> ValorOrigem MoedaOrigem  = ValorDestino MoedaDestino </h2>
</body>
</html>

Mas qual a diferença desse modo para o anterior? Afinal, parece que somente mudamos a string coringa. Como não é possível distinguir esse texto de um texto que deve realmente fazer parte do HTML de resposta do nosso site, precisaremos criar uma convenção mais forte ainda. Portanto, colocaremos esses valores dentro de chaves duplas {{}}. Assim, sempre que tivemos um par de chaves duplas, teremos que verificar o texto dentro delas, que deverá ser o nome da propriedade do nosso modelo.

<!DOCTYPE html>
<html>
<header>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">        
    <title>ByteBank</title>
</header>
<body>
    <h1>ByteBank!</h1>
    <h2> {{ValorOrigem}} {{MoedaOrigem}}  = {{ValorDestino}} {{MoedaDestino}} </h2>
</body>
</html>

Nossa View está correta, nosso modelo foi criado e estamos usando a sobrecarga na CambioController e na classe mãe. Agora só falta implementar. Vamos lá?

Usando modelos em nossa View - Implementando o código de injeção de modelo

Vamos começar a codar o código que descobrirá quais propriedades precisamos obter do nosso modelo para injetá-las no HTML. Antes de escrevermos diretamente no ControllerBase, podemos testar nossa lógica no LINQPad. Nele, criaremos um script com uma lista de statements.

No nosso código, temos inicialmente uma variável chamada viewBruta. Vamos criá-la também no LINQPad, recebendo uma simulação de código que podemos esperar no nosso HTML - por exemplo, um textos aleatórios seguidos do nome de uma propriedade entre chaves duplas.

var viewBruta = "asgdjhasgd {{Prop1}} jhskdfh {{Prop2}}";

Temos duas expressões, representadas pelos pares de chaves duplas, e queremos obter o nome contido nelas. Para isso, podemos varrer o texto caractere por caractere, definir um ponto de parada na chave aberta e continuar capturando os caracteres até o de chave fechada...mas podemos utilizar um recurso muito mais inteligente e que já existe no .NET.

Estamos trabalhando com uma expressão de formato bem definido, chamada de "expressão regular", e que é representada no .NET por uma regex, de "regular expression". Então, no LINQPad, criaremos uma nova variável regex que receberá uma instância da classe Regex do .NET.

var viewBruta = "asgdjhasgd {{Prop1}} jhskdfh {{Prop2}}";

var regex = new Regex();

Essa classe Regex espera por parâmetro a expressão regular que estamos buscando. No caso, a expressão tem uma sintaxe um pouco complicada, mas que não precisamos decorar - afinal, sempre é possível buscarmos na internet por referências e expressões semelhantes para serem adaptadas conforme o nosso uso.

var viewBruta = "asgdjhasgd {{Prop1}} jhskdfh {{Prop2}}";

var regex = new Regex("\\{{(.*?)\\}}");

Essa é a expressão regular que irá devolver qualquer texto entre um par de chaves. Com ela criada, queremos recuperar quem combina com essa expressão, o que pode ser feito com o método Matches(), buscando essa combinação na string viewBruta. Como estamos no LINQPad, usaremos o método de extensão Dump() para avaliarmos o retorno no console.

var viewBruta = "asgdjhasgd {{Prop1}} jhskdfh {{Prop2}}";

var regex = new Regex("\\{{(.*?)\\}}");
regex.Matches(viewBruta).Dump();

Como resultado, teremos várias capturas dentro dessa coleção de "matches" (quem combina com essa expressão regular). Esperamos dois iterns, um para Prop1 e outro para Prop2, e é justamente isso que receberemos.

Em "Groups", encontraremos duas capturas: a primeira incluindo todo o texto, incluindo o par de chaves; e a segunda somente com o texto dentro da expressão. Para recuperarmos os nomes das nossas propriedades, guardaremos esse retorno em uma variável matches. Usaremos o operador foreach() para iterar sobre ela, buscando pela propriedade Value do índice 1 de Groups, ou seja, o segundo grupo, que irá representar o valor interno das chaves duplas.

var viewBruta = "asgdjhasgd {{Prop1}} jhskdfh {{Prop2}}";

var regex = new Regex("\\{{(.*?)\\}}");
regex.Matches(viewBruta).Dump();

foreach(var match in matches){
    match.Groups[1].Value.Dump();
}

Entretanto, o LINQPad indicará um erro apontando que a propriedade Groups não está sendo encontrada. Isso porque o matches() não é uma coleção fortemente tipada, e está nos retornando vários objects. Corrigiremos isso fazendo um cast que converterá esse object para um objeto que representa um Match.

var viewBruta = "asgdjhasgd {{Prop1}} jhskdfh {{Prop2}}";

var regex = new Regex("\\{{(.*?)\\}}");
regex.Matches(viewBruta);

foreach(Match match in matches){
    match.Groups[1].Value.Dump();
}

Ao executarmos, teremos como retorno "Prop1" e "Prop2", que é justamente o que estávamos buscando. Com isso em mãos, sabemos qual propriedade do nosso modelo iremos visitar e onde precisaremos usar o replace(). De volta à nossa solução, representaremos uma variável regex com a nossa expressão regular, sem nos esquecermos de adicionar a diretiva using.

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);

    var regex = new Regex("\\{{(.*?)\\}}");

}

Dessa vez, não queremos exatamente retornar o nome da propriedade, mas sim utilizar substituí-lo pela propriedade do nosso modelo. Portanto, ao invés do Matches(), usaremos o método Replace(), no qual precisamos indicar qual string será verificada (viewBruta) e qual o código que retornará a string que substituirá a combinação da nossa expressão. Ou seja, para cada "match", indicaremos quem deverá ocupar o seu lugar. Com "F12", analisaremos a definição do método Replace():

public static string Replace(string input, string pattern, MatchEvaluator evaluator);

O MatchEvaluator é o item que estamos preenchendo, cuja definição podemos verificar com F12:

namespace System.Text.RegularExpressions
{
    public delegate string MatchEvaluator(Match match);
}

Repare que ele é um delegate que retorna uma string para cada match recebido por parâmetro. Portanto, para construirmos esse delegate, usaremos uma expressão regular que receberá um match por parâmetro e, para cada um deles, retornará uma string. Se passarmos "" (abre e fecha aspas), já teremos um delegate válido.

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);

    var regex = new Regex("\\{{(.*?)\\}}");
    regex.Replace(viewBruta, (match) => "");

}

Porém, queremos algo mais complexo. Sendo assim, abriremos um bloco de código no qual verificaremos qual é o nome da propriedade. Para isso, criaremos uma variável nomePropriedade, que é o que estamos buscando, e verificaremos o valor do índice 1 de Groups, ou seja, o que está dentro das chaves duplas, assim como construímos no LINQPad.

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);
    var todasAsPropriedadesDoModelo = modelo.GetType().GetProperties();

    var regex = new Regex("\\{{(.*?)\\}}");
    regex.Replace(viewBruta, (match) => {

        var nomePropriedade = match.Groups[1].Value;

    });

}

Agora que temos o nome da propriedade, falta recuperarmos o valor dela em nosso modelo. Antes da criação da regex, vamos obter todas as nossas propriedades (todasAsPropriedadesDoModelo). Para isso, usaremos o GetType(), que já utilizamos para recuperar os nossos métodos. Também podemos pegar outro tipo de membro com o método GetProperties(), que retorna todas as propriedades do nosso tipo.

Temos também o GetProperty(), no singular, de maneira semelhante ao que temos em relação ao GetMethods() e GetMethod().

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);
    var todasAsPropriedadesDoModelo = modelo.GetType().GetProperties();

    var regex = new Regex("\\{{(.*?)\\}}");
    regex.Replace(viewBruta, (match) => {

        var nomePropriedade = match.Groups[1].Value;

    });

}

Em posse de todas as propriedades do model e sabendo que uma classe possui somente nomes únicos para suas propriedades, guardaremos em uma variável auxiliar a propriedade do nossos modelo usando o método de extensão Single()para buscar a propriedade (prop) que possui o nome (Name) igual ao nomePropriedade retornado pela nossa expressão regular.

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);
    var todasAsPropriedadesDoModelo = modelo.GetType().GetProperties();

    var regex = new Regex("\\{{(.*?)\\}}");
    regex.Replace(viewBruta, (match) => {

        var nomePropriedade = match.Groups[1].Value;
        var propriedade = todasAsPropriedadesDoModelo.Single(prop => prop.Name == nomePropriedade);

    });

}

Quando tínhamos um método GetInfo, conseguíamos invocar um método sobre uma instância daquele tipo. Não temos como invocar uma propriedade, mas podemos obter o valor dela em cima de alguma instância. Para isso, usaremos o método GetValue(), que, assim como o GetInfo(), trabalha em cima de uma instância e retorna o valor dessa propriedade para aquela instância - no caso, a que foi recebida como argumento no nosso método View().

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);
    var todasAsPropriedadesDoModelo = modelo.GetType().GetProperties();

    var regex = new Regex("\\{{(.*?)\\}}");
    regex.Replace(viewBruta, (match) => {

        var nomePropriedade = match.Groups[1].Value;
        var propriedade = todasAsPropriedadesDoModelo.Single(prop => prop.Name == nomePropriedade);

        propriedade.GetValue(modelo);

    });

}

O GetValue() nos retorna um object, o que faz sentido, afinal todo esse código é dinâmico. Portanto, guardaremos esse valor em uma variável valorBruto. Como estamos escrevendo o código do nosso delegate e ainda não retornamos uma string, o Visual Studios apontará que o código é inválido. Para termos um delegate do tipo MatchEvaluator, precisaremos retornar essa string:

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);
    var todasAsPropriedadesDoModelo = modelo.GetType().GetProperties();

    var regex = new Regex("\\{{(.*?)\\}}");
    regex.Replace(viewBruta, (match) => 
    {

        var nomePropriedade = match.Groups[1].Value;
        var propriedade = todasAsPropriedadesDoModelo.Single(prop => prop.Name == nomePropriedade);

        var valorBruto = propriedade.GetValue(modelo);
        return valorBruto.ToString();

    });

}

Para tornarmos nossa solução mais robusta, adicionaremos o null propagator operator (?), que nasceu com o C# 6.0, ao nosso valorBruto. Faremos isso pois o valor retornado por nossa propriedade pode ser nulo em muitos casos, o que fará com que a invocação do método ToString() lance uma exceção do tipo Null Reference Exception

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);
    var todasAsPropriedadesDoModelo = modelo.GetType().GetProperties();

    var regex = new Regex("\\{{(.*?)\\}}");
    regex.Replace(viewBruta, (match) => 
    {

        var nomePropriedade = match.Groups[1].Value;
        var propriedade = todasAsPropriedadesDoModelo.Single(prop => prop.Name == nomePropriedade);

        var valorBruto = propriedade.GetValue(modelo);
        return valorBruto?.ToString();

    });

}

Guardaremos o que for retornado pelo Replace() em uma variável viewProcessada, e retornaremos esse conteúdo para quem chamou o método View().

protected string View(object modelo, [CallerMemberName]string nomeArquivo = null)
{
    var viewBruta = View(nomeArquivo);
    var todasAsPropriedadesDoModelo = modelo.GetType().GetProperties();

    var regex = new Regex("\\{{(.*?)\\}}");
    var viewProcessada = regex.Replace(viewBruta, (match) => 
    {

        var nomePropriedade = match.Groups[1].Value;
        var propriedade = todasAsPropriedadesDoModelo.Single(prop => prop.Name == nomePropriedade);

        var valorBruto = propriedade.GetValue(modelo);
        return valorBruto?.ToString();

    });

    return viewProcessada;

}

Como já implementamos o método em CambioController, podemos testar executando a aplicação e acessando uma URL que nos atinja a action referente ao Calculo() - no caso, http://localhost:5341/Cambio/Calculo?valor=10&moedaDestino=USD&moedaOrigem=BRL. Como retorno, teremos:

ByteBank!

10 BRL = 4.178543842480770 USD

Ou seja, nossa aplicação está funcionando! Para termos mais detalhes, colocaremos um breakpoint no método que acabamos de criar e atualizaremos a página. Como a linha de código foi atingida, pressionaremos F10 para visualizarmos o texto de viewBruta, que é exatamente o que estávamos esperando: ValorOrigem, MoedaOrigem, ValorDestino e MoedaDestino.

Já em todasAsPropriedadesDoModelo, teremos um array com 4 objetos do tipo PropertyInfo, afinal, se o nosso modelo é um CalculoCambioModel, temos quatro propriedades, MoedaDestino, MoedaOrigem, ValorOrigem e ValorDestino, que podem ser verificadas na variável.

Continuando a execução, colocaremos um breakpoint dentro do nosso delegate com F9 e clicarmeos em "Continue". Dentro do delegate, obtemos o nome da propriedade, que foi retornada pelo match e acabamos de verificar com a nossa expressão regular. Pressionando F10 para continuar, acessaremos a propriedade a partir da coleção de todas as propriedades do modelo, obteremos o valor bruto da instância para essa propriedade e o retornaremos para o nosso delegate do Replace() da expressão regular.

Sobre o curso C# Reflection parte 2: Implementando injeção de dependência do zero

O curso C# Reflection parte 2: Implementando injeção de dependência do zero possui 154 minutos de vídeos, em um total de 39 atividades. Gostou? Conheça nossos outros cursos de .NET em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda .NET acessando integralmente esse e outros cursos, comece hoje!

  • 971 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

Premium

  • 971 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$75
à vista R$900
Matricule-se

Premium Plus

  • 971 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$100
à vista R$1.200
Matricule-se

Max

  • 971 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$120
à vista R$1.440
Matricule-se
Procurando planos para empresas?
Acesso por 1 ano
Estude 24h/dia onde e quando quiser
Novos cursos toda semana