Compartilhar via


ASP.NET Core 1.0: implementando um middleware

O objetivo deste artigo é demonstrar a implementação de novos middlewares em projetos baseados no ASP.NET Core 1.0.

Introdução

A adoção de uma arquitetura baseada em middlewares é mais uma das novidades que integram o ASP.NET Core 1.0. Diversos mecanismos que compõem esta tecnologia foram implementados seguindo este conceito, o que conferiu a esta plataforma Web uma estrutura modular e mais enxuta.

Implementar novas aplicações ASP.NET envolve, em um primeiro estágio, a configuração via código de quais middlewares serão utilizados. O intuito com isto é promover uma utilização mais racional dos recursos requeridos por um projeto Web, ativando apenas as funcionalidades essenciais dentro de cada contexto.

Mas, em termos práticos, o que seria um middleware?

Um middleware nada mais do é um componente de software que faz parte do pipeline de execução de uma aplicação. A noção de pipeline se refere, por sua vez, aos diversos elementos envolvidos no fluxo de processamento de requisições e respostas de um sistema remoto (um projeto Web, por exemplo).

O comum em plataformas construídas neste padrão é que diversos middlewares participem do tratamento de solicitações HTTP, seguindo uma ordem determinada de execução. Um middleware terá a opção de repassar uma requisição ao próximo elemento na cadeia de execução ou, ainda, interromper tal fluxo caso uma condição específica não seja atendida.

A figura a seguir (obtida a partir da documentação oficial) traz uma representação esquemática do uso de middlewares no ASP.NET Core 1.0:

Além das funcionalidades oferecidas nativamente pelo novo ASP.NET, middlewares customizados também podem ser criados. Na próxima seção será apresentado um exemplo de implementação deste tipo de estrutura.

Implementando um middleware

Para a construção do projeto descrito neste artigo foram utilizados como recursos:

  • O Microsoft Visual Studio Community 2015 Update 3 como IDE de desenvolvimento;
  • O .NET Core 1.0;
  • O ASP.NET Core 1.0.

Como primeiro passo será gerado um projeto do tipo ASP.NET Core Web Application (.NET Core) chamado TesteMiddleware:

Selecionar na sequência o template Web Application em ASP.NET Core Templates:

Será necessário também adicionar os packages System.Data.Common e System.Data.SqlClient ao projeto TesteMiddleware. Estas bibliotecas possibilitarão o acesso a uma base de dados SQL Server da qual o middleware de exemplo depende:

A próxima etapa consiste na implementação de um middleware chamado ChecagemIndisponibilidade. É comum em muitos sistemas que uma aplicação fique indisponível em horários pré-estabelecidos, com isto acontecendo devido a razões como manutenções num banco de dados, tarefas agendadas envolvendo o processamento de grandes volumes de informações, dentre outros fatores.

O middleware ChecagemIndisponibilidade emitirá um aviso amigável em tais situações, evitando assim transtornos junto a usuários de uma aplicação. Isto acontecerá através do cadastramento de períodos de indisponibilidade em uma tabela do SQL Server, além de uma mensagem indicando o motivo.

Para a criação de um novo middleware é possível utilizar um template disponibilizado pelo próprio Visual Studio, através da opção Add  > New Item... em um projeto do ASP.NET Core 1.0:

Na janela Add New Item selecionar a opção Middleware Class, definindo ainda o nome do arquivo que será criado (ChecagemIndisponibiidade.cs):

A próxima listagem traz o código esperado para o middleware ChecagemIndisponibilidade, além do tipo ChecagemIndisponibilidadeExtensions.

Analisando a estrutura da classe ChecagemIndisponibilidade é possível observar:

  • A existência de um construtor que receberá como parâmetros uma instância do delegate RequestDelegate (namespace Microsoft.AspNetCore.Http), além de uma string de conexão para acesso à tabela de horários de indisponibilidade;
  • O método Invoke, o qual espera como parâmetro uma instância do tipo HttpContext (namespace Microsoft.AspNetCore.Http). Esta operação fará uma consulta a uma tabela chamada Indisponibilidade, utilizando para isto as classes SqlConnection e SqlCommand (namespace System.Data.SqlClient);
  • Caso o horário atual da requisição HTTP que se está analisando esteja fora de uma faixa de indisponibilidade, o método associado à instância do delegate RequestDelegate (atributo _next) será acionado; o objetivo com isto é repassar o processamento para o próximo middleware no pipeline de execução. Se houver um registro indicando que o sistema está indisponível então será gerado um erro de código 403 (assim como uma mensagem descrevendo o motivo), de forma a se impedir o acesso a funcionalidades da aplicação; a manipulação da resposta acontece via propriedade Response da referência do tipo HttpContext.

Já a classe estática ChecagemIndisponibilidadeExtensions conta com o Extension Method UseChecagemIndisponibilidade. Esta operação possibilitará que se configure o uso do middleware ChecagemIndisponibilidade a partir de uma instância derivada da interface IApplicationBuilder (namespace Microsoft.AspNetCore.Builder), além da string de conexão para acesso à tabela com períodos de indisponibilidade.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Data;
using System.Data.SqlClient;
 
namespace TesteMiddleware
{
    public class  ChecagemIndisponibilidade
    {
        private readonly  RequestDelegate _next;
        private readonly  string _strConexao;
 
        public ChecagemIndisponibilidade(RequestDelegate next,
            string strConexao)
        {
            _next = next;
            _strConexao = strConexao;
        }
 
        public async Task Invoke(HttpContext httpContext)
        {
            string mensagem = null;
 
            using (SqlConnection conexao = new SqlConnection(_strConexao))
            {
                conexao.Open();
 
                SqlCommand cmd = conexao.CreateCommand();
                cmd.CommandText =
                    "SELECT TOP 1 Mensagem FROM dbo.Indisponibilidade " +
                    "WHERE @DataProcessamento BETWEEN InicioIndisponibilidade " +
                      "AND TerminoIndisponibilidade " +
                    "ORDER BY InicioIndisponibilidade";
                cmd.Parameters.Add("@DataProcessamento",
                    SqlDbType.DateTime).Value = DateTime.Now;
 
                SqlDataReader reader = cmd.ExecuteReader();
                if (reader.Read())
                    mensagem = reader["Mensagem"].ToString();
 
                conexao.Close();
            }
 
            if (mensagem == null)
                await _next(httpContext);
            else
            {
                httpContext.Response.StatusCode = 403;
                await httpContext.Response.WriteAsync(
                    $"<h1>{mensagem}</h1>");
            }
        }
    }
 
    public static  class ChecagemIndisponibilidadeExtensions
    {
        public static  IApplicationBuilder UseChecagemIndisponibilidade(
            this IApplicationBuilder builder,
            string connectionString)
        {
            return builder.UseMiddleware<ChecagemIndisponibilidade>(
                connectionString);
        }
    }
}

O script para a criação da tabela Indisponibilidade está na listagem a seguir:

CREATE TABLE  [dbo].[Indisponibilidade](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [InicioIndisponibilidade] [datetime] NOT NULL,
    [TerminoIndisponibilidade] [datetime] NULL,
    [Mensagem] [varchar](MAX) NOT NULL,
 CONSTRAINT [PK_Indisponibilidade] PRIMARY KEY  ([Id])
)
GO

Também será alterado o método Configure da classe Startup, a fim de acionar o middleware ChecagemIndisponibilidade. Isto acontece por meio de uma chamada ao Extension Method UseChecagemIndisponibilidade, o qual recebe como parâmetro a string de conexão para acesso à tabela com horários de indisponibilidade:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace TesteMiddleware
{
    public class  Startup
    {
        ...
 
        public void  Configure(
            IApplicationBuilder app,
            IHostingEnvironment env,
            ILoggerFactory loggerFactory)
        {
            app.UseChecagemIndisponibilidade(
                Configuration.GetConnectionString("ExemploMiddleware"));
             
            ...
        }
    }
}

Por fim, na próxima listagem está o conteúdo do arquivo appsettings.json (contendo a string de conexão ExemploMiddleware):

{
  "ConnectionStrings": {
    "ExemploMiddleware": "STRING_CONEXÃO"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Testes

O acesso à aplicação de testes em um horário não cadastrado como indisponível será normal:

Já a próxima imagem demonstra o que acontecerá caso uma solicitação seja enviada em um horário não permitido:

A adoção de uma arquitetura baseada em middlewares é mais uma das novidades que integram o ASP.NET Core 1.0. Diversos mecanismos que compõem esta tecnologia foram implementados seguindo este conceito, o que conferiu a esta plataforma Web uma

Conclusão

Middlewares representam a base por meio da qual a arquitetura modular do ASP.NET Core 1.0 foi construída. Esta abordagem conta com um caráter plugável, sendo possível inclusive a criação de middlewares customizados que expandem as capacidades oferecidas originalmente pelo novo ASP.NET.

Por focar no tratamento de requisições HTTP ao longo de um pipeline de execução, middlewares podem ser implementados a fim de suportar tarefas como autenticação, compressão de dados e monitoramento. Além disso classes com finalidade mais geral podem ser empregadas em vários projetos, constituindo um bom exemplo de reusabilidade em termos de componentes de software.

Referências

ASP.NET Core 1.0 Documentation
http://docs.asp.net/en/latest/