C#

Criando um middleware customizado para ASP.NET Core

Em um artigo anterior expliquei o pipeline do ASP.NET Core e o funcionamento dos middlewares neste tipo de aplicação. Nele apenas citei a possibilidade de criar middleware customizados. Como nesta semana necessitei realizar este procedimento, vou explicar neste artigo como isso pode ser feito.

Definindo o middleware customizado diretamente no pipeline

A forma mais simples de definir um middleware customizado é o adicionando diretamente no pipeline no método Configure da classe Startup:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Chamou nosso middleware (antes)");
        await next();
        await context.Response.WriteAsync("Chamou nosso middleware (depois)");
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Olá Mundo!");
    });
}

Como expliquei antes, os códigos executados no pipeline são middlewares, então se adicionarmos instruções, mesmo que seja indicando o que está havendo, como a acima, elas serão consideradas um middleware.

Definindo uma classe “Middleware”

No dia a dia você irá definir middlewares mais complexos, então o ideal é defini-los em uma classe a parte:

public class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        await httpContext.Response.WriteAsync("Chamou nosso middleware (antes)");
        await _next(httpContext);
        await httpContext.Response.WriteAsync("Chamou nosso middleware (depois)");
    }
}

A classe “Middleware” obrigatoriamente precisa ter a estrutura acima. Receber no construtor um objeto RequestDelegate e definir um método Invoke que recebe por parâmetro um objeto HttpContext.

Esta classe pode ser definida no pipeline utilizando o método UseMiddleware:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMiddleware<MyMiddleware>();
}

Mas o recomendado é definir um método de extensão para a interface IApplicationBuilder:

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

E utilizá-lo no Configure:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMyMiddleware();

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Olá Mundo!");
    });
}

Este é o padrão das bibliotecas que definem middlewares.

Definindo configurações para o Middleware

Existem algumas formas de definir configurações do middleware. Uma forma muito utilizada é especificá-las no método ConfigureServices. Para isso, é necessário definir uma classe com os atributos que o middleware receberá:

public class MyMiddlewareOptions 
{
    public string BeforeMessage { get; set; } = "Chamou nosso middleware (antes)";
    public string AfterMessage { get; set; } = "Chamou nosso middleware (depois)";
}

É recomendado que se defina valores padrão para as propriedades. Assim, se novas configurações não forem definidas, os valores padrão serão utilizados.

Essas opções podem ser passadas para o middleware como uma instância da classe, ou através de uma Action<T>, que é a forma mais utilizada. Para definir isso, é necessário adicionar um método de extensão para a interface IServiceCollection:

public static class MyMiddlewareExtensions
{
    public static IServiceCollection AddMyMiddleware(this IServiceCollection service, Action<MyMiddlewareOptions> options = default)
    {
        options = options ?? (opts => { });

        service.Configure(options);
        return service;
    }

    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Isso adicionará as opções no sistema de injeção de dependências do ASP.NET, o que nos permite obtê-las no middleware através de um parâmetro do construtor da classe:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly MyMiddlewareOptions _options;

    public MyMiddleware(RequestDelegate next, IOptions<MyMiddlewareOptions> options)
    {
        _next = next;
        _options = options.Value;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        await httpContext.Response.WriteAsync(_options.BeforeMessage);
        await _next(httpContext);
        await httpContext.Response.WriteAsync(_options.AfterMessage);
    }
}

Agora o método AddMyMiddleware pode ser chamado no ConfigureServices e novas configurações podem ser indicadas:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMyMiddleware(options => {
        options.BeforeMessage = "Mensagem antes definida no ConfigureServices";
        options.AfterMessage = "Mensagem depois definida no ConfigureServices";
    });
}

Definindo as opções em uma classe, também é possível definir as configurações no arquivo appsettings.json:

{
  "MyMiddlewareOptionsSection": {
    "BeforeMessage": "Mensagem antes definida no appsettings",
    "AfterMessage": "Mensagem depois definida no appsettings"
  }
}

E indicá-las ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyMiddlewareOptions>(Configuration.GetSection("MyMiddlewareOptionsSection"));
}

Como o nome da sessão será informado, no arquivo appsettings.json, você poderia definir qualquer nome de sessão.

Com este tipo de configuração, mesmo se o middleware for adicionado mais de uma vez no pipeline, todas as versões utilizarão as mesmas configurações. Para especificar configurações para cada vez que ele for adicionado no pipeline, é necessário definir um método UseX que aceite essas configurações:

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }

    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder, Action<MyMiddlewareOptions> options = default)
    {
        var config = new MyMiddlewareOptions();
        options?.Invoke(config);
        return builder.UseMiddleware<MyMiddleware>(config);
    }
}

E modificar o construtor do middleware:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly MyMiddlewareOptions _options;

    public MyMiddleware(RequestDelegate next, MyMiddlewareOptions options)
    {
        _next = next;
        _options = options;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        await httpContext.Response.WriteAsync(_options.BeforeMessage);
        await _next(httpContext);
        await httpContext.Response.WriteAsync(_options.AfterMessage);
    }
}

Note que no lugar de aceitar um objeto de IOptions<T>, agora é aceito um objeto de MyMiddlewareOptions. Assim, o middleware pode ser adicionado duas vezes ao pipeline e receber configurações diferentes:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMyMiddleware(options => {
        options.BeforeMessage = "Primeira mensagem antes definida no Configure";
        options.AfterMessage = "Primeira mensagem depois definida no Configure";
    });

    app.UseMyMiddleware(options => {
        options.BeforeMessage = "Segunda mensagem antes definida no Configure";
        options.AfterMessage = "Segunda mensagem depois definida no Configure";
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
}

Caso queira utilizar os dois métodos, basta definir dois construtores no middleware, um aceitando objeto de IOptions<T> e outro aceitando objeto da sua classe de opções.

Conclusão

Neste exemplo exemplifiquei com um middleware simples as várias configurações disponíveis na criação de um middleware customizado. Caso trabalhe com aplicações ASP.NET, sempre que possível defina middleware customizados para manter um código limpo e reutilizar recursos.

Implementações padrão em interfaces no C# 8

A definição de interfaces na orientação a objetos as definem como “padrões definidos através de contratos que são formados com as classes“. Assim como contratos as interfaces definem cláusulas (assinaturas de métodos) que a pessoa que o assina (as classes que as implementam) deverão seguir.

Desta forma, sempre ficou muito claro que as classes são obrigadas a implementar todos os métodos declarados em uma interface. Devido a este comportamento, quando é necessário adicionar um novo método em uma interface, as opções sempre foram: também alterar todas as classes que a implementa ou criar uma nova interface.

Para mudar este cenário, no C# 8 foi adicionado um novo recurso: implementação padrão.

Implementação padrão

Agora é possível adicionar um corpo para métodos definidos em uma interface. Com isso, caso o método não seja declarado na classe ou estrutura que implementar a interface, não é gerado nenhum erro.

Vamos a um exemplo, suponha a interface abaixo:

public interface IRepositorio<T> 
{ 
    List<T> ObterTodos();
} 

Que é implementada pela classe:

public class PessoaRepositorio: IRepositorio<Pessoa>
{
    public List<Pessoa> ObterTodos()
    {
        //....
    }
}

Suponha que seja necessário alterar a interface, para adicionar um novo método. Agora é possível fazer isso sem quebrar nenhum código:

public interface IRepositorio<T>  
{ 
    List<T> ObterTodos();
    T PrimeiroRegistro() => ObterTodos().FirstOrDefault();
} 

Mesmo que a classe PessoaRepositorio não implemente este novo método, ela ainda está satisfazendo o contrato. Um objeto dela não terá acesso a ele, mas ele pode ser “convertido” para a interface, que terá acesso ao método:

public static void Primeiro(PessoaRepositorio repositorio)
{
    IRepositorio irepositorio = repositorio; // Convertendo para a interface
    var pessoa = irepositorio.PrimeiroRegistro(); //Chamando o novo método 
}

Claro que se a classe implementar o método, a declaração padrão definida na interface será ignorada. E mesmo novas classes que implementarem a interface não precisarão definir o método.

Conclusão

Não recomendo que isso seja utilizado em novas interfaces para que elas se enquadrem em várias situações. A recomendação de sempre criar uma interface enxuta e objetiva não mudou. Mas este recurso será muito útil na hora da manutenção dos códigos.

Mesmo assim, lembre-se de sempre utilizá-lo com parcimônia. Dependendo da situação, talvez a melhor opção seja criar uma nova interface e não alterar uma existente.

.NET Core 3.0 – Criando componentes no Razor Components

Em um artigo passado, falei sobre o Razor Components. Este novo tipo de aplicação, presente no .NET Core 3.0. Como dito anteriormente, o Razor Components é uma evolução do Blazor. Uma forma de criar componentes web no C#. Uma analogia seria com os componentes de frameworks JavaScript, como React e Angular.

Assim como nesses frameworks, os componentes do Razor encapsulam toda a lógica. Podendo ser adicionados em qualquer aplicação ASP.NET.

Para compreender isso, vamos colocar a mão na massa.

Este artigo está sendo escrito com base nas definições do NET Core 3 Preview 3, então no futuro algumas informações não podem ser compatíveis com a versão mais atual do .NET Core. E os exemplos demostrados aqui, requerem esta versão do framework ou uma superior.

Estrutura de um componente Razor

Pelo terminal, é possível criar uma aplicação Razor Components com o comando abaixo:

dotnet new razorcomponents -n RazorApp

Na aplicação criada, você notará uma pasta chamada Components e dentro dela localizará arquivos com a extensão *.razor:

Esses arquivos são os componentes do Razor. Ao verificar o código de um componente, teremos:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>

@functions {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

O início (@page "/counter") define a URL do componente. Dentro dela há códigos HTML e um bloco com a anotação @functions:

@functions {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

Como o nome indica, no bloco desta anotação são definidas funções invocadas pelos componentes. Elas podem ser referenciadas no HTML do arquivo através da arroba (@) como no trecho abaixo:

<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>

Qualquer código definido neste ponto pode ser referenciado no HTML com a arroba, como é o caso da variável currentCount:

<p>Current count: @currentCount</p>

Ao executar a aplicação (dotnet run), podemos acessar o componente em /counter e visualizar o seu comportamento:

Criando um componente/página Razor

Agora que conhecemos um pouco da estrutura de um componente Razor vamos criar o nosso. Este primeiro componente será uma página, então dentro da pasta Components/Pages, adicione um arquivo chamado Posts.razor e nele adicione o código abaixo:

@page "/posts"
@using RazorApp.Services
@inject BlogService blogService

<h1>Posts</h1>

@if (posts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    @foreach (var post in posts)
    {
        <div class="card" style="width: 36rem;">
            <div class="card-body">
                <h1 class="card-title">@post.Title</h1>
                <p class="card-text">@post.Content</p>
            </div>
        </div>
    }
}

@functions {
    Post[] posts;

    protected override async Task OnInitAsync()
    {
        posts = await blogService.GetPostsAsync();
    }
}

Note que nesta página estamos definindo a listagem de posts:

@foreach (var post in posts)
{
    <div class="card" style="width: 36rem;">
        <div class="card-body">
            <h1 class="card-title">@post.Title</h1>
            <p class="card-text">@post.Content</p>
        </div>
    </div>
}

Dados que virão de um service (BlogService), chamado na criação da página:

protected override async Task OnInitAsync()
{
    posts = await blogService.GetPostsAsync();
}

Assim, é necessário definir este service:

namespace RazorApp.Services
{
    public class BlogService 
    {
        public Task<Post[]> GetPostsAsync()
        {
            return Task.FromResult<Post[]>(new Post[]
            {
                new Post { Title = "Post 1", Content = "Conteúdo do Post 1" },
                new Post { Title = "Post 2", Content = "Conteúdo do Post 2" },
                new Post { Title = "Post 3", Content = "Conteúdo do Post 3" },
                new Post { Title = "Post 4", Content = "Conteúdo do Post 4" }
            });
        }
    }
}

Que contém apenas dados de exemplo. Em uma aplicação real, esta informação poderia vir do banco e/ou uma API.

Também será necessário definir o model Post:

public class Post 
{
    public string Title { get; set; }
    public string Content { get; set; }
}

“Injetar” o service na classe Startup:

public void ConfigureServices(IServiceCollection services)
{
    //Código omitido

    services.AddSingleton<BlogService>();
}

Por fim, no arquivo NavMenu.razor (presente na pasta Components/Shared), adicione um link para a página que acabamos de definir:

<li class="nav-item px-3">
    <NavLink class="nav-link" href="posts">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Posts
    </NavLink>
</li>

Ao acessar a aplicação, teremos acesso a esta página:

Simples, não é?

Criando um componente reutilizável

Agora que vimos como criar uma página, veremos como criar um componente reutilizável, que poderá ser adicionado em outras páginas e/ou componentes da aplicação.

Assim como o anterior, este componente será criado dentro da pasta Components/Pages, agora com o nome de LikeButton.razor e nele adicione o código abaixo:

<button onclick="@Liked" 
        class="btn @( likedCount == 0 ? "btn-outline-secondary" : "btn-outline-danger" )">
    <i class="far fa-heart"></i>
    @likedCount
</button>

@functions {
    [Parameter] int InitialLikedCount { get; set; }

    int likedCount = 0;

    protected override void OnInit()
    {
        likedCount = InitialLikedCount;
    }

    void Liked()
    {
        likedCount++;
    }
}

Neste componente estamos definindo um botão:

<button onclick="@Liked" 
        class="btn @( likedCount == 0 ? "btn-outline-secondary" : "btn-outline-danger" )">
    <i class="far fa-heart"></i>
    @likedCount
</button>

Este botão contém o ícone de coração do font-awesome, então é necessário adicionar a referência dela na página Index.cshml (localizada em Pages). No bloco @functions, é definido um parâmetro:

[Parameter] int InitialLikedCount { get; set; }

Que será atribuído a variável likedCount na inicialização da página:

protected override void OnInit()
{
    likedCount = InitialLikedCount;
}

Como é possível notar, ao definir [Parameter] , indicamos que o valor da variável irá como um atributo do componente.

Já no método Liked a variável likedCount é incrementada:

void Liked()
{
    likedCount++;
}

Para utilizar este componente, basta defini-lo em outra página como uma tag:

<LikedButton>

Faça isso no componente que criamos anteriormente:

@foreach (var post in posts)
{
    <div class="card" style="width: 36rem;">
        <div class="card-body">
            <h1 class="card-title">@post.Title</h1>
            <p class="card-text">@post.Content</p>
        </div>
        <div class="card-footer">
            <LikeButton />
        </div>
    </div>
}

Inicialmente não estamos passando o parâmetro para o componente, mesmo assim ele funciona:

Caso seja passado o parâmetro:

@foreach (var post in posts)
{
    <div class="card" style="width: 36rem;">
        <div class="card-body">
            <h1 class="card-title">@post.Title</h1>
            <p class="card-text">@post.Content</p>
        </div>
        <div class="card-footer">
            <LikeButton InitialLikedCount="5" />
        </div>
    </div>
}

O componente será inicializado com o valor informado:

Conclusão

Este é um exemplo simples de criação e reutilização de componentes, mas nele é possível notar o poder deste recurso. Tenho certeza que não irá demorar muito para termos componentes para ecossistemas específicos, como Bootstrap, Material, etc.

Afinal: o que é de fato o LINQ?

Certamente, uma das features mais interessantes e legais que o .NET e seu ecossistema oferece é o LINQ. LINQ é um acrônimo para Language Integrated Query, ou Consulta Integrada à Linguagem . Trata-se de um “framework” dentro do .NET destinado a auxiliar os desenvolvedores a escrever expressões de consulta diretamente em C# de maneira agnóstica.

Mas o que exatamente é o LINQ?

Para entender melhor: pense nas seguintes hipóteses dentro de um software:

  • Se o software em questão precisar realizar consultas em um banco de dados relacional, provavelmente será necessário escrever consultas SQL;
  • Se o software em questão precisar realizar consultas em um arquivo XML, provavelmente será necessário utilizar expressões XPath e/ou XQuery;
  • Se o software em questão precisar realizar consultas em coleções de objetos, provavelmente será necessário utilizar blocos de código com a linguagem na qual o software está sendo desenvolvido.

Essa complexidade é adicionada pelo fato de estarmos tratando de fontes de dados heterogêneas de dados, cada qual com suas linguagens de manipulação específicas. Ou seja: se seu software se conecta a uma base de dados relacional e manipula objetos, você provavelmente precisará desenvolver e dar manutenção em pelo menos três linguagens diferentes: SQL, XPath (ou XQuery) e a linguagem utilizada para desenvolver a aplicação em si.
O LINQ tem como objetivo justamente remover essa complexidade, pois ele abstrai a complexidade envolvida na utilização de diferentes linguagens de consulta, como SQL, xPath e xQuery. Essa abstração é feita em cima de uma API de alto nível compatível com as linguagens integrantes do .NET Framework. Ou seja: você consegue consultar uma base de dados relacional, um arquivo XML uma coleção de objetos através de uma API unificada, invocada através de uma linguagem integrante do .NET Framework. Trazendo para um exemplo mais palpável: você consegue unicamente com código C# fazer consultas a conjuntos de objetos, bases de dados relacionais e arquivos XML, sendo o LINQ o encarregado de fazer a devida “tradução” para cada uma das fontes a serem consultadas.

Providers LINQ e árvores de expressão (expression trees)

O LINQ consegue trabalhar de maneira agnóstica (ou seja, a mesma API do LINQ consegue trabalhar em cima de várias fontes de dados diferentes) principalmente por causa de dois pilares: os providers e as árvores de expressão – ou expression trees. Para entendermos cada um deles, vamos a partir deste ponto adotar o C# como a linguagem de referência.

De maneira geral e simplificada, uma expression tree é um trecho de código organizado como uma árvore onde cada nó pode corresponder a um valor, uma chamada de método ou até mesmo um operador aritmético ou lógico. No .NET, as árvores de expressão são definidas pela classe Expression, advinda do namespace System.Linq.Expressions.
Vamos montar uma árvore de expressão simples: uma árvore que retorna true caso receba um número menor que 5 ou retorna false no caso contrário. Para isso, precisamos criar um delegate que seja capaz de realizar essa análise… No .NET, existem dois tipos de delegate especializados e já disponibilizados pela API do LINQ:

  • Func: é um delegate que indica que um valor vai ser retornado. Delegates do tipo Func podem não receber parâmetros, mas sempre tem que prover algum retorno;
  • Action: é um delegate que indica que nenhum valor será retornado. Delegates do tipo Action podem não receber parâmetros e nunca irão retornar um valor.

Se verificarmos a situação acima, veremos que um delegate do tipo Func com um parâmetro de entrada do tipo int e uma saída do tipo boolean é o mais adequado. Sendo assim, utilizando expressões-lambda, podemos escrever esta definição:

Func<int, bool> expr = num => num < 5;

Para que este delegate se torne uma árvore de expressão de verdade, é necessário que ele seja envolvido pela classe Expression:

Expression<Func<int, bool>> expr = num => num < 5;

Agora, temos uma árvore de expressão completa com três ramificações:

  • O argumento do tipo int de entrada;
  • O operador de comparação >, revelando que se trata de uma árvore de expressão binária;
  • O 5, um valor constante que deve ser utilizado para comparação.

Qualquer expressão LINQ é decomposta nestas árvores de expressão. Neste momento, entra em cena os providers LINQ.
Os providers LINQ são utilizados basicamente para fazer a tradução das árvores de expressão para cada uma das fontes de dados na qual a expressão LINQ está conectada. Os principais providers LINQ são:

  • LINQ to objects: utilizado quando a fonte de consulta é um conjunto de objetos;
  • LINQ to XML: utilizado quando a fonte de consulta é um XML;
  • LINQ to SQL: utilizado quando a fonte de consulta é um banco de dados relacional.

As principais estruturas dos providers LINQ são as interfaces IQueryProvidere IQueryable. Todos os providers LINQ e suas variações são obrigados a implementar estas interfaces.
Através das implementações dos providers LINQ, as expressões de consulta podem ser traduzidas para as suas respectivas fontes de dados… Por exemplo: vamos considerar a árvore de expressão anterior (Expression expr = num => num < 5) com o número 4 como entrada.

Se estivéssemos falando de LINQ to Objects, cada nó que faz parte da expressão seria traduzido para o código C# correspondente. Teríamos o resultado abaixo:

Se estivéssemos falando de LINQ to SQL, cada nó que faz parte da mesma expressão seria traduzido para o código SQL correspondente. Teríamos o resultado abaixo:

Já se estivéssemos falando de LINQ to XML, cada nó que faz parte da mesma expressão seria traduzido para o código xPath/xQuery correspondente. Teríamos o resultado abaixo:

Veja que o LINQ, de acordo com a fonte de dados que está sendo analisada, utiliza o provider para realizar uma “tradução” da API de alto nível em C# para o código correspondente ao tipo da fonte de dados que está sendo utilizada. E esse processo torna a API completamente independente da fonte de dados a qual esta se conecta: quem fica responsável por dizer ao LINQ como cada nó da árvore de expressão tem que ser traduzido é o provider.

Existem muito mais coisas do LINQ a serem exploradas!

O LINQ é de fato uma ferramenta muito poderosa quando utilizada corretamente (especialmente quando estamos falando do LINQ to SQL). Este primeiro post na verdade tem a intenção de mostrar os princípios básicos e o funcionamento interno do LINQ. Nos próximos posts, iremos começar a analisar alguns pontos interessantes para quem utiliza o LINQ no dia-a-dia.

Criando um provider customizado para o Microsoft.Extensions.Logging

Caso já tenha desenvolvido uma aplicação ASP.NET Core, deve ter deparado com o seu sistema de log. Adicionado desde a primeira versão desta biblioteca, o ILoggingBuilder da Microsoft.Extensions.Logging é o responsável por registrar nos Services provedores de log para este tipo de aplicação.

Internamente, o provedor recebe do LoggerFactory todas as informações geradas durante a execução da aplicação, tanto em tempo de desenvolvimento quanto em produção.

Mesmo com uma série de provedores (nativos e de terceiros), em alguns casos é necessário criar um provedor customizado e neste artigo veremos como isso pode ser feito.

Anteriormente em um projeto ASP.NET Core…

Antes de vermos a criação do provedor, um resumo da biblioteca Microsoft.Extensions.Logging.

Esta biblioteca foi criada como uma API de log, que os desenvolvedores podem utilizar para capturar os logs internos do ASP.NET Core, bem como gerar logs customizados. Esses logs são enviados para provedores, que ficam responsáveis de registrar as mensagens.

No momento, há os seguintes provedores nativos:

E também temos os seguintes provedores de bibliotecas de terceiro:

Caso nenhuma dessas bibliotecas atendam as suas necessidades, chega a hora de criar um provedor customizado, é o que faremos a seguir.

Primeiro Ato – Preparando o ambiente

Neste exemplo, iremos criar um provedor que registre os logs dos dados em uma tabela do SQLite, então antes de criá-lo precisamos preparar o ambiente.

Neste artigo estou utilizando uma aplicação ASP.NET Core 2.2. Então, após a criação dela, adicione a biblioteca do SQLite:

dotnet add package System.Data.SQLite

E do Dapper (caso queira, você pode optar por outra biblioteca ORM):

dotnet add package Dapper

Já a configuração desta conexão, está seguindo o mesmo padrão do meu artigo do Dapper, que você pode ver aqui. A diferença é que para o provedor será definida a entidade/model abaixo:

public class EventLog
{
    public int Id { get; set; }
    public string Category { get; set; }
    public int EventId { get; set; }
    public string LogLevel { get; set; }
    public string Message { get; set; }
    public DateTime CreatedTime { get; set; }
}

No próximo tópico veremos a implementação do repositório desta entidade.

Segundo Ato – Criando o provedor

Para criar um provedor para o LoggerFactory é necessário criar ao menos duas classes, que implementem, respectivamente, as interfaces ILogger e ILoggerProvider. Também é recomendado a criação de um método de extensão, seguindo o padrão Add{nome provedor}, que facilitará a sua adição.

E como implementaremos um provedor para o SQLite, implementaremos um repositório abstrato. Este repositório será uma propriedade da nossa classe “ILogger. Assim, de início defina este repositório:

public abstract class LoggerRepository : AbstractRepository<EventLog>
{
    public LoggerRepository(IConfiguration configuration) : base(configuration){}
}

Esta classe herda a classe AbstractRepository que demonstrei no artigo do Dapper.

Aproveitando, já defina a EventLogRepository:

public class EventLogRepository : LoggerRepository
{
    public EventLogRepository(IConfiguration configuration) : base(configuration){}

    public override void Add(EventLog item)
    {
        using (IDbConnection dbConnection = new SQLiteConnection(ConnectionString))
        {
            string sQuery = "INSERT INTO EventLog (Category, EventId, LogLevel, Message, CreatedTime)"
                            + " VALUES(@Category, @EventId, @LogLevel, @Message, @CreatedTime)";
            dbConnection.Open();
            dbConnection.Execute(sQuery, item);
        }
    }

    public override IEnumerable<EventLog> FindAll()
    {
        using (IDbConnection dbConnection = new SQLiteConnection(ConnectionString))
        {
            dbConnection.Open();
            return dbConnection.Query<EventLog>("SELECT * FROM EventLog");
        }
    }

    public override EventLog FindByID(int id)
    {
        using (IDbConnection dbConnection = new SQLiteConnection(ConnectionString))
        {
            string sQuery = "SELECT * FROM EventLog" 
                        + " WHERE Id = @Id";
            dbConnection.Open();
            return dbConnection.Query<EventLog>(sQuery, new { Id = id }).FirstOrDefault();
        }
    }

    public override void Remove(int id)
    {
        using (IDbConnection dbConnection = new SQLiteConnection(ConnectionString))
        {
            string sQuery = "DELETE FROM EventLog" 
                        + " WHERE Id = @Id";
            dbConnection.Open();
            dbConnection.Execute(sQuery, new { Id = id });
        }
    }

    public override void Update(EventLog item)
    {
        using (IDbConnection dbConnection = new SQLiteConnection(ConnectionString))
        {
            string sQuery = "UPDATE EventLog SET Category = @Category,"
                        + " EventId = @EventId, LogLevel= @LogLevel," 
                        + " Message = @Message, CreatedTime= @CreatedTime" 
                        + " WHERE Id = @Id";
            dbConnection.Open();
            dbConnection.Query(sQuery, item);
        }
    }
}

Agora podemos definir a nossa classe ILogger, que será chamada SqliteLogger:

public class SqliteLogger<T> : ILogger where T: LoggerRepository
{
    private Func<string, LogLevel, bool> _filter;
    private T _repository;
    private string _categoryName;
    private readonly int maxLength = 1024;
    private IExternalScopeProvider ScopeProvider { get; set; }

    public SqliteLogger(Func<string, LogLevel, bool> filter, T repository, string categoryName)
    {
        _filter = filter;
        _repository = repository;
        _categoryName = categoryName;
    }

    public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state) ?? NullScope.Instance;

    public bool IsEnabled(LogLevel logLevel) => (_filter == null || _filter(_categoryName, logLevel));

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        var logBuilder = new StringBuilder();

        if (!IsEnabled(logLevel)) 
        { 
            return; 
        } 
        if (formatter == null) 
        { 
            throw new ArgumentNullException(nameof(formatter)); 
        } 
        var message = formatter(state, exception);
        if (!string.IsNullOrEmpty(message)) 
        { 
            logBuilder.Append(message);
            logBuilder.Append(Environment.NewLine);
        }

        GetScope(logBuilder);

        if (exception != null)
            logBuilder.Append(exception.ToString());

        if (logBuilder.Capacity > maxLength)
            logBuilder.Capacity = maxLength;

        var eventLog = new EventLog 
        { 
            Message = message, 
            EventId = eventId.Id,
            Category = _categoryName,
            LogLevel = logLevel.ToString(), 
            CreatedTime = DateTime.UtcNow 
        };

        _repository.Add(eventLog);
    }

    private void GetScope(StringBuilder stringBuilder)
    {
        var scopeProvider = ScopeProvider;
        if (scopeProvider != null)
        {
            var initialLength = stringBuilder.Length;

            scopeProvider.ForEachScope((scope, state) =>
            {
                var (builder, length) = state;
                var first = length == builder.Length;
                builder.Append(first ? "=> " : " => ").Append(scope);
            }, (stringBuilder, initialLength));

            stringBuilder.AppendLine();
        }
    }
}

Note que esta é uma classe parametrizada, que define que o tipo de dados deve herdar a classe LoggerRepository:

public class SqliteLogger<T> : ILogger where T: LoggerRepository

No construtor da classe, é recebido um predicado, o repositório e a categoria:

public SqliteLogger(Func<string, LogLevel, bool> filter, T repository, string categoryName)
{
    _filter = filter;
    _repository = repository;
    _categoryName = categoryName;
}

O predicado é um filtro que definirá qual nível de log deve ser registrado. Já a categoria é o valor definido na criação do logger.

No método BeginScope, adicionamos os escopos definidos em um provider de escopo:

public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state) ?? NullScope.Instance;

Esses escopos são iterados e posteriormente adicionados na mensagem que será salva no banco.

No método IsEnabled:

public bool IsEnabled(LogLevel logLevel) => (_filter == null || _filter(_categoryName, logLevel));

Verifica, de acordo com o filtro, se o log deve ser registrado ou não.

Já no método Log é onde montamos a nossa mensagem e a salvamos no banco de dados (através do repositório).

Com o SqliteLogger definido, podemos criar o provedor em si:

public class SqliteLoggerProvider<T>: ILoggerProvider where T : LoggerRepository
{
    private readonly Func<string, LogLevel, bool> _filter;
    private readonly T _repository;

    public SqliteLoggerProvider(Func<string, LogLevel, bool> filter, T repository)
    {
        this._filter = filter;
        this._repository = repository;
    }

    public ILogger CreateLogger(string categoryName) => new SqliteLogger<T>(_filter, _repository, categoryName);

    public void Dispose() {}
}

Esta classe também recebe no seu construtor um predicado de filtro e o repositório. Já no método CreateLogger é retornado uma instância do nosso logger, passando a categoria informada:

public ILogger CreateLogger(string categoryName) => new SqliteLogger<T>(_filter, _repository, categoryName);

Agora é necessário definir o método de extensão:

public static class SqliteLoggerExtensions
{
    public static ILoggingBuilder AddSqliteProvider<T>(this ILoggingBuilder builder, T repository) where T: LoggerRepository
    {
        builder.Services.AddSingleton<ILoggerProvider, SqliteLoggerProvider<T>>(p => new SqliteLoggerProvider<T>((_, logLevel) => logLevel >= LogLevel.Debug, repository));

        return builder;
    }
}

Neste método de extensão é definido que o level mínimo do log é o Debug, que é o mais baixo. Isso significa que todas as mensagens de log serão registrados.

Terceiro Ato – A hora da verdade

Com o provedor definido, podemos utilizá-lo. Na atual versão do ASP.NET Core (2.2), isso deve ser feito no método CreateWebHostBuilder da classe Program, conforme abaixo:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureLogging((hostingContext, logging) =>
        {
            logging.AddSqliteProvider(new EventLogRepository(hostingContext.Configuration));
        });

Ao executar a aplicação, todos os logs do ASP.NET Core serão salvos no nosso banco de dados:

Qualquer log definido dentro da aplicação, como o abaixo:

public class HomeController : Controller
{
    private readonly ILogger _logger;

    public HomeController(ILogger<HomeController> logger) => _logger = logger;

    public IActionResult Index()
    {
        _logger.LogInformation("Chamando a página inicial do site");

        return View();
    }

    //...
}

Também será salvo no banco.

Epílogo

Mesmo com várias opções de provedores para o Microsoft.Extensions.Logging, às vezes há a necessidade de criar um provedor customizado. Felizmente, como vimos, a criação deste tipo de provedor não é complexa. Então, quando não achar uma solução que o atende, opte pela implementação customizada.

Você pode ver o código da aplicação utilizada neste artigo no meu Github, aqui.

.NET Core 3.0 – Razor Components

No final do mês de janeiro, foi lançado o segundo preview do .NET Core 3.0. Nele foi introduzido o Razor Components, conhecido anteriormente como Blazor server-side.

Para os que não conhecem, o Blazor é um projeto experimental da Microsoft que tem por objetivo levar uma aplicação .NET ao navegador utilizando WebAssembly. Pense em uma aplicação Angular ou React, mas utilizando C# e Razor.

No momento o Blazor trata-se apenas de um experimento, mas a equipe do ASP .NET pretende adicionar na versão final do .NET Core 3.0 o Razor Components.

Então vamos conhecê-lo.

Razor Components

O desenvolvimento web tem evoluído de várias maneiras ao longo dos últimos anos. Hoje há uma infinidade de frameworks front-end e N formas de se criar aplicações web. Mesmo assim, ainda há desafios na criação de uma aplicação web moderna.

Com o objetivo de sanar alguns desses desafios que se iniciou o “experimento” Blazor e dele, agora temos o Razor Components.

Funcionamento

Diferente do Blazor, no Razor Components o código C# não é executado no navegador. Ele é processado no servidor, que se comunica com a interface através do SignalR:

Na prática, ao acessar uma aplicação criada com o Razor Components, ela se comportará como uma SPA (Single Page Application). Será baixado um arquivo index.html e um arquivo Javascript chamado blazor.server.js. Em seguida a aplicação estabelecerá uma conexão com o servidor através do SignalR.

No servidor, o componente da página será processado gerando o seu HTML. Este é comparado “instantaneamente” com a versão presente no client. Caso haja alguma diferença, ela é comparada, as alterações são enviadas para o client, o navegador “desempacota” o pacote e aplica as alterações no DOM do client.

Quando há alguma interação com a tela (como o clique de um botão), o evento é comparado e enviado para o servidor pela mesma conexão. Onde ele é processado e retorna o resultado, com as alterações que devem ser aplicadas ao DOM.

Vantagens

Há várias vantagens em utilizar o Razor Components:

  • .NET Core APIs: Por executar no servidor, a aplicação pode fazer uso das várias APIs disponíveis no .NET Core. Além de reaproveitar códigos de outras aplicações C#, se for necessário. Sendo possível até utilizar os componentes do Razor em aplicações ASP.NET MVC (no momento o inverso ainda não é possível);
  • Aparência de SPA: Como disse antes, uma aplicação Razor Components se comporta como uma aplicação SPA, mesmo que não seja desenvolvida como tradicionalmente este tipo de aplicação é criada;
  • Estabilidade e performance: A equipe do .NET Core procura sempre melhorar a performance do framework. Então ao utilizá-lo, já há a garantia que a aplicação será performática, estável e segura;
  • Rapidez no carregamento: Comparado com o Blazor, a aplicação do Razor Components envia para o client apenas dois arquivos leves, que são baixados rapidamente;
  • Suporte de vários navegadores: A principal desvantagem do Blazor é o suporte apenas dos navegadores que suportam o WebAssembly. Já o Razor Components é suportado praticamente por todos os navegadores, incluindo navegadores móveis.

Desvantagens

Infelizmente não existe almoço grátis, o Razor Components também possui algumas desvantagens:

  • Online only: Como o código C# é executado no servidor, a aplicação necessita sempre se comunicar com ele quando ocorrer qualquer tipo de interação na página. Assim, não é possível gerar uma versão offline da aplicação;
  • Latência: Mesmo o SignalR sendo muito eficiente, ainda pode haver alguns engasgos na comunicação do client com o servidor. Principalmente porque qualquer interação do usuário com a aplicação precisa ser enviada para o servidor, processada e um DOM resultante retornado;
  • Persistência do estado da aplicação: No momento se a comunicação com o servidor for perdida, o estado da aplicação também será perdido. E por enquanto não há muitas soluções para este cenário.

Encerrando

Vou finalizar este artigo por aqui. Agora que você conhece um pouco do Razor Components, no próximo vou mostrar um pouco de código. Veremos como criar este tipo de aplicação e a criação de alguns componentes.

Até a próxima.

Criando uma API RESTful com o NancyFx e .NET Core

Quando pensamos em API RESTful no C#, a primeira opção que vem a mente é o ASP.NET Web API. Ele é um ótimo framework, utilizado em vários sistemas, com uma abundante documentação e material de apoio. Mas ele não é a nossa única opção para a criação de API Rest.

Em artigos passados, conhecemos o ServiceStack, mas outra opção que está crescendo é o NancyFX.

Inspirado no framework Sinatra do Ruby, o Nancy é um framework de criação de serviços HTTP, leve e direto ao ponto. Tem por objetivo não atrapalhar o desenvolvedor. Ele obtém isso através de uma série de configurações padrões e convenções. Assim, com o Nancy é possível criar um site em minutos.

Claro que todas as suas convenções e configurações podem ser alteradas, caso este seja o desejo do desenvolvedor.

Para este artigo, mostrarei como é possível criar uma API simples, rapidamente utilizando o Nancy.

Criando a aplicação

Neste artigo estou utilizando o .NET Core, desta forma, mostrarei como criar a aplicação através do terminal. Inicialmente é necessário criar uma aplicação web vazia:

dotnet new web -n NancyAPI

No projeto criado, adicione a referência do Nancy:

dotnet add package Nancy --version 2.0.0-clinteastwood

Agora é necessário dizer ao ASP.NET que iremos utilizar o Nancy, para isso, faremos uso do OWIN.

O OWIN significa “Open Web Interface for .Net. Ele é um conjunto de padrões voltados para o .NET, que visa facilitar e encorajar a implementação de projetos que tentam desacoplar a aplicação do servidor. Na prática ele define um middleware ao pipeline de execução de uma aplicação ASP.NET.

Por se tratar de um middleware, deve ser configurado no método Configure da classe Startup:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseOwin(o => o.UseNancy());
}

Com isso, o Nancy já está ativo na nossa aplicação.

Módulos

No Nancy os endpoints devem ser definidos em módulos. Um módulo nada mais é que uma classe que herde a classe NancyModule. Esta classe pode ser definida em qualquer ponto do projeto, pois o Nancy fará uma busca pelos módulos quando um endpoint for invocado. O único ponto importante é que esta classe seja definida como pública (public) para que possa ser localizada.

Por exemplo:

using Nancy;

namespace NancyAPI.Module
{
    public class HomeModule : NancyModule
    {
        public HomeModule()
        {
            Get("/", _ => "Hello World from Nancy!");
        }
    }
}

Note que a rota está sendo definida como o primeiro parâmetro do método Get. Como é possível supor, esta rota será invocada quando for solicitada através de uma requisição GET.

O Nancy define métodos para os principais verbos do HTTP:

  • Delete = DELETE;
  • Get = GET;
  • Head = HEAD;
  • Options = OPTIONS;
  • Post = POST;
  • Put = PUT;
  • Patch = PATCH.

No segundo parâmetro do método deve ser passado a referência de uma função ( uma Function), que no exemplo acima, se utiliza lambda para definir a função. Este é o padrão recomendado pelo Nancy.

Ao executar a aplicação e acessar o endpoint definido, teremos o resultado:

Simples, não é?

Definindo a API RESTful

Para exemplificar uma API REST, vamos definir um modelo:

namespace NancyAPI.Models
{
    public class Pessoa
    {
        public int Id { get; set; }
        public string Nome { get; set; }
    }
}

E um repositório:

using System.Collections.Generic;
using NancyAPI.Models;
using System.Linq;

namespace NancyAPI.Repositories
{
    public class PessoaRepository
    {
        private static Dictionary<int, Pessoa> pessoas = new Dictionary<int, Pessoa>();

        public List<Pessoa> GetAll(){
            return pessoas.Values.ToList();
        }

        public Pessoa Get(int id){
            return pessoas.GetValueOrDefault(id);
        }

        public void Add(Pessoa pessoa){
            pessoas.Add(pessoa.Id, pessoa);
        }

        public void Edit(Pessoa pessoa){
            pessoas.Remove(pessoa.Id);
            pessoas.Add(pessoa.Id, pessoa);
        }

        public void Delete(int id){
            pessoas.Remove(id);
        }
    }
}

Agora só falta o nosso endpoint:

using Nancy;
using Nancy.ModelBinding;
using NancyAPI.Models;
using NancyAPI.Repositories;

namespace NancyAPI.Module
{
    public class PessoaModule: NancyModule
    {
        public readonly PessoaRepository repository;
        public PessoaModule()
        {
            repository = new PessoaRepository();

            Get("/pessoa/", _ => repository.GetAll());
            Get("/pessoa/{id}", args => repository.Get(args.id));
            Post("/pessoa/", args => {
                var pessoa = this.Bind<Pessoa>();

                repository.Add(pessoa);

                return pessoa;
            });
            Put("/pessoa/{id}", args => {
                var pessoa = this.Bind<Pessoa>();

                pessoa.Id = args.id;

                repository.Edit(pessoa);

                return pessoa;
            });
        }
    }
}

Neste endpoint é possível notar que os argumentos da URL são obtidos através do parâmetro do lambda:

Get("/pessoa/{id}", args => repository.Get(args.id));

Este parâmetro é um objeto dinâmico. Assim, o Nancy tentará obter o valor do argumento com base na propriedade informada.

Já os dados passados na solicitação, podem ser obtidos pelo Bind:

var pessoa = this.Bind<Pessoa>();

O Nancy irá converter automaticamente os dados obtidos para o tipo de objeto indicado.

Para testar os endpoints, podemos utilizar o Postman:

POST

GET

PUT

GET ID

Conclusão

Graças a sua estrutura simples, porém sofisticada, o Nancy tem atraído cada vez mais a atenção dos desenvolvedores. Se tornando uma ótima alternativa ao ASP.NET Web API.

Caso necessite criar uma API REST, não esqueça de dar uma boa olhada nele.

Você pode ver o projeto apresentado neste artigo no meu GitHub.

Documentando uma ASP.NET Core Web API com o Swagger

Com cada vez mais aplicações multiplataformas, é comum definir uma API REST que será consumida pelas várias versões da aplicação. Mas com vários times tendo acesso a API, não basta mais apenas desenvolvê-la e esperar que todos consigam utilizá-la institivamente.

Na época do web service, existia o WSDL que funcionava como uma documentação, já que facilitava a criação dos clientes que iria consumí-lo. Com as APIs REST não temos esta facilidade, assim, torna-se imprescindível que ela seja bem documentada.

Mas como realizar esta documentação? Existem algumas ferramentas que podem nos auxiliar neste processo, como: API Blueprint, RAML, Swagger, entre outras.

Como o nome deste artigo sugere, aqui abordaremos o Swagger.

Swagger

O Swagger é uma aplicação open source que auxilia os desenvolvedores a definir, criar, documentar e consumir APIs REST. Sendo uma das ferramentas mais utilizadas para esta função, a empresa por trás dela (a SmartBear Software), decidiu criar o Open API Iniciative e renomearam as especificações do Swagger para OpenAPI Specification.

Ela visa padronizar as APIs REST, desta forma descreve os recursos que uma API deve possuir, como endpoints, dados recebidos, dados retornados, códigos HTTP, métodos de autenticação, entre outros.

Para facilitar o processo de especificação/documentação de uma API, o Swagger fornece algumas ferramentas, como:

  • Swagger Editor: Editor que permite especificar uma API. Há uma versão online grátis e aplicativos para desktop;
  • Swagger UI: Interface interativa para a documentação da API;
  • Swagger Codegen: Gera templates de código (mais de 20 linguagens estão disponíveis) de acordo com uma especificação de API.

Neste artigo vamos focar na documentação de uma API existente, criada em ASP.NET Core Web API.

Especificação do Swagger

O ponto mais importante do Swagger é a sua especificação, que era chamada de Swagger specification e agora é OpenAPI Specification. Esta especificação trata-se de um documento, JSON ou YAML, que define a estrutura da API. Indicando os endpoints, formatos de entrada, de saída, exemplos de requisições, forma de autenticação, etc.

Abaixo você pode ver um exemplo desta especificação definida em JSON:

{
   "swagger": "2.0",
   "info": {
       "version": "v1",
       "title": "API V1"
   },
   "basePath": "/",
   "paths": {
       "/api/Example": {
           "get": {
               "tags": [
                   "Example"
               ],
               "operationId": "ApiExampleGet",
               "consumes": [],
               "produces": [
                   "text/plain",
                   "application/json",
                   "text/json"
               ],
               "responses": {
                   "200": {
                       "description": "Success",
                       "schema": {
                           "type": "array",
                           "items": {
                               "$ref": "#/definitions/Item"
                           }
                       }
                   }
                }
           },
           "post": {
               ...
           }
       },
       "/api/Example/{id}": {
           "get": {
               ...
           },
           "put": {
               ...
           },
           "delete": {
               ...
   },
   "definitions": {
       "Item": {
           "type": "object",
            "properties": {
                "id": {
                    "format": "int64",
                    "type": "integer"
                },
                "name": {
                    "type": "string"
                },
                "isValid": {
                    "default": false,
                    "type": "boolean"
                }
            }
       }
   },
   "securityDefinitions": {}
}

É possível definir esta especificação na mão, olhando as opções na documentação, através do Swagger Editor ou utilizando outra ferramenta para gerá-la.

No ASP.NET Core podemos utilizar duas bibliotecas para gerar este documento:

Por ser mais completa, faremos uso da Swashbuckle.

Adicionando o Swashbuckle na aplicação

Para este exemplo, estou utilizando uma aplicação ASP.NET Core Web API, baseada no exemplo fornecido pela documentação da Microsoft, que pode ser vista aqui. Você pode ver a aplicação que criei no meu Github.

Com a aplicação Web API criada, inicialmente é necessário adicionar o pacote do Swashbuckle na aplicação. Ele possui três componentes:

Mas é necessário instalar apenas o pacote Swashbuckle.AspNetCore. Por se tratar de uma aplicação .NET Core, este pacote pode ser adicionado com o comando abaixo:

dotnet add package Swashbuckle.AspNetCore

Após isso, para que o arquivo de especificação da API do Swagger seja criado, é necessário adicionar o gerador dele nos serviços da aplicação, no método ConfigureServices da classe Startup:

public void ConfigureServices(IServiceCollection services)
{
    //... código omitido

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info { Title = "TodoAPI", Version = "v1" });
    });
}

Para a classe Info, defina o using do namespace abaixo:

using Swashbuckle.AspNetCore.Swagger;

Agora, no método Configure é necessário ativar o Swagger e indicar o local onde o seu arquivo de especificação será criado:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    //Ativa o Swagger
    app.UseSwagger();

    // Ativa o Swagger UI
    app.UseSwaggerUI(opt =>
    {
        opt.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoAPI V1");
    });

    //... código omitido
}

Com isso, através de reflection a biblioteca consegue especificar os endpoints da aplicação.

Caso seja executada, podemos acessar o Swagger UI da aplicação no caminho /swagger:

Caso queira que o Swagger UI seja acessado a partir da raiz da aplicação, na ativação dela, no método Configure, atribua vazio para a propriedade RoutePrefix:

app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoAPI V1");
    c.RoutePrefix = string.Empty;
});

Customizando o Swagger

Como o Swashbuckle trabalha com reflection, podemos fazê-lo gerar mais informações das nossas APIs com base em configurações definidas nela.

Comentários de documentação

Sabemos que no C# podemos adicionar comentários de documentação nos nossos códigos, como:

/// <summary>
/// Lista os itens da To-do list.
/// </summary>
/// <returns>Os itens da To-do list</returns>
/// <response code="200">Returna os itens da To-do list cadastrados</response>
[HttpGet]
public ActionResult<List<Item>> Get()
{
    return _repository.GetAll();
}

É possível fazer o Swashbuckle ler esses comentários e assim tornar a especificação da API mais detalhada.

Para isso, na definição de geração do arquivo de especificação, no método ConfigureServices deve ser adicionado a informação abaixo:

services.AddSwaggerGen(opt =>
{
    opt.SwaggerDoc("v1", new Info { Title = "TodoAPI", Version = "v1" });

    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    opt.IncludeXmlComments(xmlPath);
});

Note que estamos indicando que ele deve ler os comentários do código a partir de um arquivo XML. Para que eles sejam gerados neste arquivo indicado, no arquivo de configuração do projeto (o .csproj), adicione o trecho abaixo:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
  <NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>

O <NoWarn> é definido para que a IDE não exiba um alerta sobre os códigos que não possuírem comentários de documentação.

Agora ao acessar o Swagger API, o que foi definido em <summary> será mostrado:

Também podemos o <remarks> para indicar um exemplo de requisição:

// POST api/todo
/// <summary>
/// Cria um item na To-do list.
/// </summary>
/// <remarks>
/// Exemplo:
///
///     POST /Todo
///     {
///        "id": 1,
///        "name": "Item1",
///        "iscomplete": true
///     }
///
/// </remarks>
/// <param name="value"></param>
/// <returns>Um novo item criado</returns>
/// <response code="201">Retorna o novo item criado</response>
/// <response code="400">Se o item não for criado</response>        
[HttpPost]
public ActionResult<Item> Post([FromBody] Item value)
{
    Console.WriteLine(value?.Name);

    var item = _repository.Save(value);
    if(item != null)
        return item;

    return BadRequest();
}

O resultado será:

Com as anotações ProducesResponseType e Produces podemos indicar os códigos HTTP e o tipo de dado que os endpoints irão retornar:

[Authorize]
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
public class TodoController : ControllerBase
{
    //..código omitido

    // GET api/todo
    /// <summary>
    /// Lista os itens da To-do list.
    /// </summary>
    /// <returns>Os itens da To-do list</returns>
    /// <response code="200">Returna os itens da To-do list cadastrados</response>
    [HttpGet]
    [ProducesResponseType(200)]
    public ActionResult<List<Item>> Get()
    {
        return _repository.GetAll();
    }

    //... código omitido
}

Especificando a autenticação

Uma das principais vantagens de adicionar o Swagger na aplicação, é poder testar os endpoints pela Swagger UI. Quando a aplicação define autenticação, é necessário indicar isso para o Swashbuckle, para que ele também gere uma especificação desde detalhe.

Com isso, será possível testar os endpoints pela Swagger UI utilizando a autenticação definida.

Para fazer isso, devemos adicionar a informação abaixo, na definição de geração do arquivo de especificação, no método ConfigureServices:

services.AddSwaggerGen(opt =>
{
    opt.SwaggerDoc("v1", new Info { Title = "TodoAPI", Version = "v1" });

    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    opt.IncludeXmlComments(xmlPath);

    var security = new Dictionary<string, IEnumerable<string>>
    {
        {"Bearer", new string[] { }},
    };

    opt.AddSecurityDefinition(
        "Bearer",
        new ApiKeyScheme
        {
            In = "header",
            Description = "Copie 'Bearer ' + token'",
            Name = "Authorization",
            Type = "apiKey"
        });

    opt.AddSecurityRequirement(security);
});

Acima estamos definindo que a aplicação faz uso de autenticação do tipo JWT Token (“Bearer Token”). Na documentação do Swagger, você pode ver os outros tipos de autenticação suportados.

Ao acessar o Swagger UI, ele irá mostrar o botão “Autorize”:

Exibe um botão com o texto "Autorize"

Ao clicar nele, é possível informar o tipo de autenticação especificado:

Com isso, mesmo que necessite de autenticação, será possível realizar as requisições dos endpoints da aplicação pelo Swagger UI.

Descrição

Por fim, é possível descrever um pouco da aplicação através das propriedades da classe Info da especificação do Swagger:

services.AddSwaggerGen(opt =>
{
    opt.SwaggerDoc("v1", new Info
    {
        Version = "v1",
        Title = "Todo API",
        Description = "Um exemplo de aplicação ASP.NET Core Web API",
        TermsOfService = "Não aplicável",
        Contact = new Contact
        {
            Name = "Wladimilson",
            Email = "contato@treinaweb.com.br",
            Url = "https://treinaweb.com.br"
        },
        License = new License
        {
            Name = "CC BY",
            Url = "https://creativecommons.org/licenses/by/4.0"
        }
    });

    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    opt.IncludeXmlComments(xmlPath);

    var security = new Dictionary<string, IEnumerable<string>>
    {
        {"Bearer", new string[] { }},
    };

    opt.AddSecurityDefinition(
        "Bearer",
        new ApiKeyScheme
        {
            In = "header",
            Description = "Copie 'Bearer ' + token'",
            Name = "Authorization",
            Type = "apiKey"
        });

    opt.AddSecurityRequirement(security);
});

O resultado será:

Swagger UI mostrando a descrição da API

Conclusão

O Swagger é uma ótima forma de documentar APIs REST e com a biblioteca Swashbuckle, podemos gerar o arquivo de especificação do Swagger com facilidade. Assim, não há mais desculpa para se definir documentações interativas e detalhadas para as suas APIs REST.

Compreendendo os middlewares no ASP.NET Core

Desde a sua primeira versão, o ASP.NET faz uso dos middlewares. Eles foram implementados como uma forma de modularizar uma aplicação ASP.NET facilmente.

Em termos práticos, middleware seria um trecho de código que pode ser executado no fluxo de execução da aplicação. No ASP.NET os middleware são organizados em um pipeline e são executados conforme uma solicitação é recebida e uma resposta enviada. A imagem abaixo ilustra este pipeline:

A imagem apresenta uma solicitação sendo processada por três middlewares, sendo que cada um chamado o middleware seguinte. No terceiro middleware, uma resposta é retornada e esta passa pelos middleware chamados anteriormente.

Cada middleware pode executar uma ação no recebimento da solicitação, chamar o próximo middleware, utilizando o método next(), e executar outra ação durante o retorno da resposta. Só não é possível modificar a resposta no seu retorno.

Dependendo da funcionalidade, o middleware pode decidir não chamar o próximo no pipeline, não invocando o método next(). Por exemple, o middleware de arquivos estáticos pode retornar uma solicitação para um arquivo estático e interromper o fluxo restante.

É possível definir middleware de diversas funcionalidades, por exemplo, no trecho de código abaixo, temos oito middlewares:

  1. Exception/error handling
  2. HTTP Strict Transport Security Protocol
  3. HTTPS redirection
  4. Static file server
  5. Cookie policy enforcement
  6. Authentication
  7. Session
  8. MVC
public void Configure(IApplicationBuilder app)
{
    if (env.IsDevelopment())
    {
        // Quando executado em desenvolvimento:
        //   Utiliza Developer Exception Page para reportar erros.
        //   Utiliza Database Error Page para reportar erros do banco.
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        // Quando não estiver em produção:
        //   Habilita o middleware Exception Handler Middleware para pegar os erros.
        //   Utiliza o middleware que habilita o 
        //       HTTP Strict Transport Security Protocol (HSTS)
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    // Utiliza o middleware HTTPS Redirection que redireciona solicitações HTTP para HTTPS.
    app.UseHttpsRedirection();

    // Retorna arquivos estáticos e interrompe o pipeline.
    app.UseStaticFiles();

    // Utiliza o middleware Cookie Policy, que está em conformidade com 
    // as regras do GDPR (General Data Protection Regulation).
    app.UseCookiePolicy();

    // Autentica antes de utilizar os recursos.
    app.UseAuthentication();

    // Se o aplicativo utiliza sessão, chama o middleware Session depois do middleware 
    // Cookie Policy e antes do middleware MVC.
    app.UseSession();

    // Adiciona MVC ao pipeline da solicitação
    app.UseMvc();
}

Como é possível notar, no ASP.NET todos os middlewares são definidos no método Configure no padrão Use* seguindo do nome do middleware. Neste método também é possível adicionar middlewares customizados, utilizando os métodos Use ou Run. A diferença entres eles é que os middlewares definidos com Run são middlewares “finais”, após eles, nenhum outro middleware é chamado.

Entendendo a ordem do pipeline

Para compreender a ordem de execução dos middlewares no pipeline, vamos definir alguns middlewares simples:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Primeiro middleware (antes)");
        await next();
        await context.Response.WriteAsync("Primeiro middleware (depois)");
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Segundo middleware (antes)");
        await next();
        await context.Response.WriteAsync("Segundo middleware (depois)");
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Middleware final");
    });
}

Neste exemplo os middlewares estão sendo definidos como métodos anônimos, em um artigo futuro mostrarei as formas de declará-los.

Neste ponto o importante é que sabia que os middlewares definidos com Use recebem dois parâmetros: a instância de um objeto HttpContext e a instância do delegate RequestDelegate, que aponta para o próximo middleware no pipeline.

O resultado do código acima será algo assim:

Primeiro middleware (antes)
Segundo middleware (antes)
Middleware final
Segundo middleware (depois)
Primeiro middleware (depois)

Note que a ordem que esses middlewares estiverem definidos no método Configure irá indicar a ordem que eles serão chamados no pipeline. Ao chegar o último middleware do pipeline, os middlewares anteriores são chamados novamente.

Short-circuiting middleware

Caso algum dos middlewares não invocar o método next(), o pipeline será encerrado antes do seu final:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Primeiro middleware (antes)");
        await next();
        await context.Response.WriteAsync("Primeiro middleware (depois)");
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Segundo middleware (antes)");
        // await next();
        await context.Response.WriteAsync("Segundo middleware (depois)");
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Middleware final");
    });
}

O resultado será:

Primeiro middleware (antes)
Segundo middleware (antes)
Segundo middleware (depois)
Primeiro middleware (depois)

Um middleware não final, que não invoca o método next, é chamado de short-circuiting middleware.

Dividindo o pipeline

Nos exemplos anteriores, o nosso pipeline possuía apenas um fluxo: o primeiro middleware sempre vem antes do segundo e por fim é chamado o middleware final. Mas não precisa ser sempre desta forma. Dependendo da solicitação pode ser definido um fluxo diferente para o pipeline.

Criando novo fluxo final

Com os métodos Map ou MapWhen é possível definir um novo fluxo final para o pipeline. O método Map permite especificar um middleware que será invocado de acordo com o caminho da solicitação. Já o MapWhen possui mais poder porque o padrão pode ser definido utilizando o objeto HttpContext:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Primeiro middleware (antes)");
        await next();
        await context.Response.WriteAsync("Primeiro middleware (depois)");
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Segundo middleware (antes)");
        await next();
        await context.Response.WriteAsync("Segundo middleware (depois)");
    });

    app.Map("/foo",
        (a) => {
            a.Use(async (context, next) => {
                await context.Response.WriteAsync("Middleware para o caminho /foo (antes) ");
                await next();
                await context.Response.WriteAsync("Middleware para o caminho /foo (depois) ");
            });
    });

    app.MapWhen(context => context.Request.Path.StartsWithSegments("/bar"), 
        (a) => {
            a.Use(async (context, next) => {
                await context.Response.WriteAsync("Middleware para o caminho /bar (antes) ");
                await next();
                await context.Response.WriteAsync("Middleware para o caminho /bar (depois) ");
            });
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Middleware final");
    });
}

Mesmo sendo possível invocar o método next() nos middlewares definidos com Map e MapWhen, por não existir nenhum middleware seguinte, ao ser invocado é gerado um erro. Por exemplo, se a solicitação possuir um caminho /foo será gerado um fluxo assim:

Lembrando que também há a volta.

Se os métodos next() forem omitidos dos middlewares definidos em Map e MapWhen, teríamos um fluxo assim:

Primeiro middleware (antes)
Segundo middleware (antes)
Middleware para o caminho /foo (antes)
Middleware para o caminho /foo (depois)
Segundo middleware (depois)
Primeiro middleware (depois)

Criando um fluxo alternativo no pipeline

Para evitar o problema do uso do next() nos middlewares definidos em Map e MapWhen, é possível utilizar o método UseWhen, que funciona da mesma forma que o MapWhen, com a diferença que após executá-lo o fluxo do pipeline retorna ao caminho padrão:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Primeiro middleware (antes)");
        await next();
        await context.Response.WriteAsync("Primeiro middleware (depois)");
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Segundo middleware (antes)");
        await next();
        await context.Response.WriteAsync("Segundo middleware (depois)");
    });

    app.Map("/foo",
        (a) => {
            a.Use(async (context, next) => {
                await context.Response.WriteAsync("Middleware para o caminho /foo (antes) ");
                await next();
                await context.Response.WriteAsync("Middleware para o caminho /foo (depois) ");
            });
    });

    app.UseWhen(context => context.Request.Path.StartsWithSegments("/bar"), 
        (a) => {
            a.Use(async (context, next) => {
                await context.Response.WriteAsync("Middleware para o caminho /bar (antes) ");
                await next();
                await context.Response.WriteAsync("Middleware para o caminho /bar (depois) ");
            });
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Middleware final");
    });
}

Caso a requisição tenha o caminho /bar, o resultado do pipeline será:

Primeiro middleware (antes)
Segundo middleware (antes)
Middleware para o caminho /bar (antes)
Middleware final
Middleware para o caminho /bar (depois)
Segundo middleware (depois)
Primeiro middleware (depois)

Podemos ilustrar isso na imagem abaixo:

Conclusão

Como é possível ver, a ideia por trás dos middlewares no ASP.NET Core é simples, porém muito poderosa. Muitos recursos do framework são disponibilizados para as aplicações através de middlewares.

Por isso o conhecimento do funcionamento do seu pipeline é um requisito obrigatório de todo desenvolvedor ASP.NET Core.

Novos recursos do ASP.NET Core 2.1

No final do mês de maio deste ano, o .NET Core foi atualizado para a versão 2.1, e com ela, novos recursos foram adicionados na aplicação padrão do ASP.NET MVC. Para conhecê-los, vamos criar uma aplicação para esta versão.

Criando a aplicação

Crie a aplicação com o comando abaixo:

dotnet new mvc -n AspNetCore2_1Exemplo -au Individual

Ao executar o projeto, a primeira coisa que notará é que ele carregará duas URLs:

Uma HTTP e outra HTTPS. Seguindo as recomendações de que toda aplicação deve implementar SSL, no ASP.NET Core 2.1, o template padrão já adiciona o suporte a este protocolo na aplicação. Assim, ao acessá-la, sempre seremos redirecionados para a versão HTTPS:

Outro recurso adicionado no template padrão desta versão é a exibição da mensagem indicando o uso de cookie:

Isso é algo que segue a recomendação do GDPR.

Você pode adicionar este recurso em um projeto configurando o uso do middleware CookiePolicyMiddleware, que pode ser feito, adicionando a linha abaixo na configuração da sua aplicação:

app.UseCookiePolicy();

Para ela sempre sempre redirecionada para a versão HTTPS, adicione a linha abaixo:

app.UseHttpsRedirection();

Quanto à mensagem do uso de cookies, você pode alterá-la no arquivo Views/Shared/_CookieConsentPartial.cshtml:

@using Microsoft.AspNetCore.Http.Features

@{
    var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
    var showBanner = !consentFeature?.CanTrack ?? false;
    var cookieString = consentFeature?.CreateConsentCookie();
}

@if (showBanner)
{
    <nav id="cookieConsent" class="navbar navbar-default navbar-fixed-top" role="alert">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#cookieConsent .navbar-collapse">
                    <span class="sr-only">Toggle cookie consent banner</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <span class="navbar-brand"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span></span>
            </div>
            <div class="collapse navbar-collapse">
                <p class="navbar-text">
                    Use this space to summarize your privacy and cookie use policy.
                </p>
                <div class="navbar-right">
                    <a asp-controller="Home" asp-action="Privacy" class="btn btn-info navbar-btn">Learn More</a>
                    <button type="button" class="btn btn-default navbar-btn" data-cookie-string="@cookieString">Accept</button>
                </div>
            </div>
        </div>
    </nav>
    <script>
        (function () {
            document.querySelector("#cookieConsent button[data-cookie-string]").addEventListener("click", function (el) {
                document.cookie = el.target.dataset.cookieString;
                document.querySelector("#cookieConsent").classList.add("hidden");
            }, false);
        })();
    </script>
}

Por fim, ao registrar um usuário e acessar a página de gerenciamento da conta, é possível ver o link da opção para exportar os dados e excluir a conta:

Isso também foi implementado devido às regras do GDPR. Também por causa dessas regras, é criada uma página onde deve ser adicionado a política de privacidade da aplicação:

Ela não aparece no menu, mas se encontra no caminho: Home/Privacy

Conclusão

O ASP.Net Core 2.1 adiciona uma série de recursos que facilitam a adequação da aplicação às mais novas regras da internet, reduzindo a necessidade de codificação e preocupação com estes detalhes, deixando o programador focado apenas com a lógica de negócio do projeto.

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES