Interface IApplicationLifetime

Desde o ASP clássico nós temos um arquivo chamado Global.asa. Com o ASP.NET, este arquivo foi renomeado para Global.asax. A finalidade deste arquivo é fornecer uma forma de interceptar eventos que ocorrem no nível da aplicação. Entre estes eventos, nós temos um que é disparado indicando que a aplicação foi inicializada, outro que algum erro não tratado aconteceu, que uma nova requisição chegou, que a aplicação está sendo encerrada, etc.

Este arquivo já não existe mais no ASP.NET Core, porém a necessidade ainda existe em alguns tipos de aplicação. Para interceptar a inicialização da aplicação e o término dela, podemos recorrer à interface IApplicationLifetime. Esta interface fornece três propriedades que indica a inicialização da aplicação (ApplicationStarted), que ela está sendo encerrada (ApplicationStopping) e depois de encerrada (ApplicationStopped). Essas propriedades são do tipo CancellationToken, e através de um delegate do tipo Action, passamos para o método Register o código customizado que queremos executar quando estes eventos acontecerem.

Para exemplificar o uso e utilidade desta interface, considere o exemplo abaixo onde temos uma classe estática que gerencia o agendamento e execução de tarefas pelo Quartz.NET. Além do método Schedule, temos os métodos Start e Stop, que como já podemos perceber, queremos invocar cada um deles no início e no fim da aplicação.

public class TaskManager
{
    public static void Start()
    {
        Scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;
        Scheduler.Start();
    }

    private static IScheduler Scheduler { get; set; }

    public async static Task Schedule<TJob>(IDictionary data) where TJob : IJob
    {
        await Scheduler.ScheduleJob(
            JobBuilder
                .Create<TJob>()
                .SetJobData(new JobDataMap(data))
                .Build(),
            TriggerBuilder
                .Create()
                .WithSimpleSchedule(x => x.WithRepeatCount(0))
                .Build());
    }

    public static void Shutdown()
    {
        Scheduler?.Shutdown(true);
    }
}

O método Start deverá ser chamado quando a aplicação estiver sendo inicializada. Já no momento, do encerramento, recorremos ao método Shutdown do Schedule, passando o parâmetro true, que indica que antes de encerrar o gerenciador de tarefas, é necessário aguardar o término de alguma que, eventualmente, ainda esteja em execução. A interface IApplicationLifetime, assim como diversas outras do ASP.NET Core, o próprio runtime cria a instância de uma implementação padrão e passa para o método Configure da nossa classe Startup. Com isso, basta associarmos os métodos aos respectivos eventos, conforme é possível visualizar abaixo:

public void Configure(
    IApplicationBuilder app, 
    IHostingEnvironment env, 
    ILoggerFactory log, 
    IApplicationLifetime lifetime)
{
    lifetime.ApplicationStarted.Register(() => TaskManager.Start());
    lifetime.ApplicationStopped.Register(() => TaskManager.Shutdown());

    //outras configurações
}

Por fim, a aplicação (os controllers) interagem com a classe TaskManager a partir do método Schedule<TJob>, indicando qual a tarefa que precisa ser executada e parâmetros contextuais que queremos passar para a implementação da mesma.

[HttpPost]
public async Task<IActionResult> AgendarExecucao()
{
    await TaskManager
        .Schedule<GeracaoDeDados>(
            Request.Form.ToDictionary(x => x.Key, x => x.Value.ToString()));

    return View("ExecucaoAgendada");
}
Anúncios

Segregando Requisições no ASP.NET

No ASP.NET Core temos o conceito de middlewares, que nada mais são que plugins que podemos acoplar à execução para que eles sejam executados a medida em que uma nova requisição chega para o serviço ou para o site. Além de vários que já foram criados pela Microsoft, a criação destes middlewares é extremamente simples, e é a principal forma que temos de interceptar o momento antes e/ou depois das requisições.

Durante a inicialização da aplicação, devemos realizar as configurações necessárias para que ela funcione, e é neste momento que montamos o pipeline por onde cada uma delas deverá passar, e isso quer dizer que faremos uso constante de middlewares, mesmo que não se esteja explicitamente claro no código que vamos escrever. A execução ocorre de forma linear, onde a requisição é entregue para o primeiro middleware e este é responsável por fazer alguma atividade e passar adiante a requisição (leia-se para o middleware interno). Abaixo temos um exemplo de um simples middleware; note que temos algumas convenções que precisam ser respeitadas, tais como: receber no construtor o próximo middleware, ter o método chamado Invoke que será disparado pelo ASP.NET no momento adequado. É dentro deste método que você define quando quer invocar o próximo middleware.

public class LoggingMiddleware
{
    private readonly RequestDelegate next;

    public LoggingMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        //Ação Antes
        await next(context);
        //Ação Depois
    }
}

O problema aqui é que podemos hospedar na mesma aplicação dois conteúdos que demandam diferentes rotinas e características. É comum encontrarmos aplicações que expõe uma API web, e na mesma aplicação, fornece o acesso à um pequeno site com a documentação, tutoriais, central de suporte para a mesma. Do lado da API, é comum termos log de acesso, telemetria, controle de transações, etc., enquanto do “lado do UI” da aplicação, necessitamos de outros recursos, como a exposição de conteúdos estáticos e geração de HTML.

O ASP.NET possibilita uma forma de segregar as requisições (branch), roteando cada uma delas de acordo com um critério e, que se atendido, podemos customizar um pipeline de middlewares exclusivo para aquele cenário. Isso evita ter um único pipeline com todos os passos e ter que dar by-pass de acordo com a requisição que estamos processando naquele momento exige. Para isso, podemos recorrer ao método Map da interface IApplicationBuilder. Em sua versão mais simplista, ele aceita uma string que permite especificarmos a URL que, se atendida, deverá ser executada utilizando a configuração que especificamos no segundo parâmetro deste mesmo método. Há uma variação deste método chamado MapWhen, que nos permite inspecionar outros itens da requisição (headers, por exemplo) para tomar a decisão se deve ou não ser processada por aquele pipeline.

No exemplo abaixo, se a requisição conter /commands na URL, então o middleware de logging será disparado, na sequência o de transações, e por fim, a ação do controller será executada. Já quando a requisição for para /help, estamos permitindo o acesso à arquivos estáticos (imagens, PDFs, etc.) e também o uso do MVC para renderizar HTML com o conteúdo da documentação da API:

public void Configure(IApplicationBuilder app)
{
    app.Map("/commands",
        config =>
            config
                .UseMiddleware<LoggingMiddleware>()
                .UseMiddleware<TransactionMiddleware>()
                .UseMvc());

    app.Map("/help", 
        config => 
            config
                .UseMvc()
                .UseStaticFiles());
}

Se desejar ter acesso ao código completo deste artigo, consulte este endereço.

Middlewares para Comandos

Quando optamos por separar a leitura da escrita em nossa aplicação, é comum adotarmos o padrão CQRS. Entre os vários conceitos que temos, um deles é a criação de comandos, que nada mais são do que classes que expressam a intenção do usuário em alterar, criar ou excluir alguma informação. O comando é definido através de uma classe, que traz informações (dados) sobre a ação a ser executada. Ele é passado para o que chamamos de command handler, que é o tratador deste comando, ou seja, ele coordena todas as ações sobre aquela entidade. E, da mesma forma, também é implementada através de uma classe (apesar de haver algumas variações). Em geral, a relação um tratador para cada comando.

O código abaixo exibe um exemplo simples disso. A classe Command não traz qualquer informação adicional, é apenas uma convenção; já a interface IHandler<T> define a estrutura que todo tratador de comando deverá ter, ou seja, o método Handle. É no interior deste método que recorremos ao repositório para adicionar/remover a entidade, manipular, etc.

public class CadastrarUsuario : Command
{
    public string Nome { get; set; }
}

public class CadastrarUsuarioHandler : IHandler<CadastrarUsuario>
{
    public CadastrarUsuarioHandler(IRepositorioDeUsuarios repositorio) { }

    public void Handle(CadastrarUsuario message)
    {
        Console.WriteLine("Cadastrando o usuário " + message.Nome);
    }
}

Para manter a simplicidade, este código está apenas escrevendo o nome do usuário na tela. Em um ambiente real, é provável termos que incrementar isso. Para citar alguns exemplos, é comum ter a necessidade de realizar logs, mensurar o tempo gasto na operação, proteger as ações através de uma transação, etc. A partir do momento que começarmos a incorporar essas atividades em nosso tratador, ele começará a ficar poluído, e o pior, começa a ter mais de uma responsabilidade além daquela para qual ele foi criado. Note o mesmo tratador que temos acima, contendo estes incrementos:

public class CadastrarUsuarioHandler : IHandler<CadastrarUsuario>
{
    public CadastrarUsuarioHandler(
        ILogger logger, 
        ITimer timer, 
        IUnitOfWork transaction, 
        IRepositorioDeUsuarios repositorio) { }

    public void Handle(CadastrarUsuario message)
    {
        this.timer.Start();
        this.logger.Info("Início");

        using (this.transaction)
        {
            this.repositorio.Salvar(message.Nome);
            Console.WriteLine("Cadastrando o usuário " + message.Nome);
        }

        this.logger.Info("Fim");
        this.timer.Stop();
    }
}

Mesmo que estamos recebendo as implementações do mundo exterior, a classe CadastrarUsuarioHandler está manipulando mais coisas do que deveria. Quando criarmos um segundo comando, teremos que repetir todos estes códigos, ficando sujeito a erros e com pouca reusabilidade.

O melhor a fazermos aqui é remover todo este código de cross-cutting, deixando o tratador do comando responsável apenas por criar e armazenar o usuário na base de dados através do repositório. Todas essas outras – importantes – atividades podem ser delegadas para classes que circundam todos os tratadores da aplicação, não ficando restrito a apenas um, e a partir daí, tendo um ponto central para isso, fica fácil a manutenção e eventual ajuste na interface dos componentes, já que não precisamos sair refatorando em diversos lugares da aplicação.

Uma das técnicas para isso, é criar um pipeline de funcionalidades, onde o item mais baixo de ações é a execução do tratador do comando. Até ele chegar no tratador propriamente dito, ele passaria pelas funcionalidades de log, timer e transação, deixando o ambiente todo preparado para a execução da atividade principal. A ideia é criar middlewares, próximo ao que temos no ASP.NET Core, para que possamos acoplar ou desacoplar alguma funcionalidade quando necessário. Além da extensibilidade, temos uma separação bem definida do que cada middleware deve fazer, sem um afetar no trabalho doutro. A imagem abaixo ilustra o exemplo:

commandhandlermiddleware1

O primeiro passo é a criação da interface que irá definir a estrutura de um middleware. Basicamente ele fornecerá um método chamado Execute, que além da instância do comando a ser executado, ela também recebe o método a ser executado na sequência. Opcionalmente, você pode optar por receber o delegate da próxima ação no construtor do middleware.

public interface IMiddleware
{
    void Execute(Command command, Action<Command> next);
}

A partir daí podemos ir criando os middlewares necessários para a nossa aplicação. Abaixo temos o exemplo do TimerMiddleware, que utiliza um Stopwatch para mensurar o tempo gasto na execução do comando. É importante notar que podemos ter códigos sendo executado antes e depois da próxima ação.

public class TimerMiddleware : IMiddleware
{
    public void Execute(Command command, Action<Command> next)
    {
        var timer = Stopwatch.StartNew();
        Console.WriteLine("[INFO] - Início - {0:HH:mm:ss}", DateTime.Now);

        try
        {
            next(command);
        }
        finally
        {
            timer.Stop();
        }

        Console.WriteLine("[INFO] - Fim - {0:HH:mm:ss} - Tempo Decorrido: {1}", 
            DateTime.Now, timer.Elapsed);
    }
}

Só que este middleware por si só não funciona. Temos agora que criar uma estrutura que consiga receber os middlewares e, consequentemente, montar uma cadeia de chamadas na ordem desejada para execução. A ideia é receber todos os middlewares necessários através do construtor do pipeline, e com isso, já criar uma sequência de execução, para que ela já esteja pronta quando chegar para o pipeline um novo comando. É importante notar aqui que neste cenário, o command bus (mencionado em arquitetura CQRS) é implementado como um middleware.

public class Pipeline
{
    private Action<Command> middlewareChain;

    public Pipeline(params IMiddleware[] middlewares)
    {
        this.middlewareChain = BuildChain(middlewares.Reverse().ToArray());
    }

    private static Action<Command> BuildChain(IMiddleware[] middlewares)
    {
        Action<Command> chain = command => { };

        foreach (var middleware in middlewares)
        {
            var temp = chain;

            chain = command => middleware.Execute(command, temp);
        }

        return chain;
    }

    public virtual void Handle<T>(T command) where T : Command
    {
        this.middlewareChain(command);
    }
}

Se quiser postergar a criação da cadeia de chamada para a primeira execução, podemos declarar o campo middlewareChain como Lazy<T>. E, por fim, basta instanciarmos o pipeline de acordo com a nossa estrutura de middlewares necessária para a aplicação, e a partir daí, passar os comandos que estão sendo requisitados pelos usuários. Novamente, note que o middleware CommandHandlerMiddleware é o último da lista:

var pipeline = 
    new Pipeline(
        new TimerMiddleware(), 
        new LoggingMiddleware(), 
        new TransactionMiddleware(), 
        new CommandHandlerMiddleware());

pipeline.Handle(new CadastrarUsuario() { Nome = "Israel" });

O código na íntegra está disponível neste link.

Request Features no ASP.NET

O ASP.NET 5 está sendo construído para ser completamente independente do hosting em que ele está sendo executado, permitindo assim, que este tipo de aplicação seja também levado para outras plataformas (Linux e Mac). Atualmente o ASP.NET 5 pode ser hospedado no IIS (incluindo a versão Express), via self-host (WebListener) ou através do Kestrel (que é um servidor multi-plataforma baseado no libuv.

Mesmo que seja independente do hosting, o ASP.NET fornece um conjunto de tipos que permite interagir, de forma genérica e abstrata, com as funcionalidades que são expostas pelo HTTP, e que na maioria das vezes, são criadas e abastecidas pelo receptor da requisição, ou seja, o próprio hosting. Esses tipos são chamados de request features, e estão incluídas no pacote chamado Microsoft.AspNet.Http.Features. Entre os tipos (funcionalidades) disponíveis temos as interfaces IHttpRequestFeatureIHttpResponseFeature, que representam o básico para o funcionamento de qualquer servidor web, que é receber a requisição, processar e devolver o resultado. Um outro exemplo é a interface ITlsConnectionFeature, que pode ser utilizada para interagir com os certificados enviados pelo cliente. Para uma relação completa, consulte a documentação.

Cada um dos servidores disponíveis já implementam as interfaces definidas (desde que eles suportem), e nos permite acessar essas funcionalidades durante a execução da aplicação, incluindo a possibilidade de acesso através de qualquer middleware. A classe HttpContext expõe uma propriedade chamada Features, que nada mais é que uma coleção das funcionalidades que são incluídas (e, novamente, suportadas) pelo hosting onde a aplicação está rodando. Essa coleção fornece um método genérico chamado Get<TFeature>, onde podemos definir a funcionalidade que queremos acessar. Abaixo um trecho do código de exemplo:

public Task Invoke(HttpContext httpContext)
{
    var clientCertificate =
        httpContext.Features.Get<ITlsConnectionFeature>()?.ClientCertificate;

    if (clientCertificate != null)
    {
        //…
    }

    return _next(httpContext);
}

Além da possibilidade de extrair alguma funcionalidade, é possível também incluir funcionalidade durante a execução, e como já era de se esperar, basta utilizar o método, também genérico, Set<TFeature>. E o mais interessante é que não estamos restritos a utilizar uma das interfaces expostas pelo ASP.NET; podemos criar novas funcionalidades e incorporá-las na execução.

Considere o exemplo abaixo, onde criamos uma funcionalidade para simular a proteção da requisição por uma transação. Basta utilizarmos uma interface para descrever a funcionalidade e criar uma classe que a implemente.

public interface ITransactionFeature
{
    string TransactionId { get; }
    void Commit();
    void Abort();
}

public class TransactionFeatureContext : ITransactionFeature
{
    public string TransactionId { get; } = Guid.NewGuid().ToString();

    public void Commit()
    {
        //…
    }

    public void Abort()
    {
       //…
    }
}

A classe TransactionFeatureContext faz toda a gestão da transação, desde a sua criação (identificando-a) e expõe métodos para conclusão com sucesso (Commit) ou falha (Abort). Repare que tanto a interface quanto a classe não referenciam nenhum tipo do ASP.NET; estamos livres para determinarmos toda a estrutura da nossa funcionalidade. Só que estas classes precisam ser incorporadas na execução, e para isso, vamos criar um middelware que incluirá esta funcionalidade na coleção de Features do contexto da requisição.

public class TransactionMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext httpContext)
    {
        var transactionContext = new TransactionFeatureContext();
        httpContext.Features.Set<ITransactionFeature>(transactionContext);

        try
        {
            await _next(httpContext);
            transactionContext.Commit();
        }
        catch
        {
            transactionContext.Abort();
            throw;
        }
    }
}

O middleware é criado é os middlewares internos que serão invocados a partir dele estarão sendo gerenciados pela transação. Qualquer exceção não tratada que ocorra, a transação será automaticamente abortada. Por fim, depois do middleware criado, incluímos ele na aplicação através do método UseMiddleware.

Vale lembrar que a ordem em que se adiciona os middlewares são importantes. Como a classe TransactionMiddleware adiciona a feature TransactionFeature que criamos, os middlewares que vem a seguir podem acessar a funcionalidade através do método Get<TFeature>, conforme falado acima. Opcionalmente podemos criar na interface métodos para que os middlewares da sequência possam votar no sucesso ou falha e este, por sua vez, avalia os votos antes de chamar o método Commit.

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();
    app.UseTransactionMiddleware();
    app.UseClientCertificateLogMiddleware();
    app.UseMvc();
}

Convenções no ASP.NET

Durante a construção e evolução do ASP.NET MVC e Web API a Microsoft foi assumindo algumas regras, que durante a execução, os frameworks alteram e geram informações de acordo com o código escrito por nós. Isso é conhecido como convention-over-configuration, e tem como foco diminuir algumas configurações explícitas por parte do desenvolvedor, focando em detalhes mais importantes para atender o negócio e o framework se responsabiliza por realizar as configurações necessárias para o funcionamento daquele código respeitando as tais convenções.

Apesar de as vezes não notarmos, isso está bastante presente em nosso dia a dia. Quando criamos um controller, dentro dele uma ação e esta, por sua vez, retornarmos uma view, o ASP.NET MVC assume a estrutura física de Views/NomeDoController/NomeDaAcao.cshtml para armazenar o arquivo e localiza-lo para exibir para o cliente. Outro exemplo é quando utilizamos o Entity Framework e queremos que a entidade Cliente seja persistida na base de dados; o ORM irá criar, automaticamente, a tabela com o mesmo nome daquela entidade. Já no ASP.NET Web API, se criarmos uma ação com o método chamado Get, ele estará automaticamente acessível através do verbo GET do HTTP, sem precisar decorar o mesmo com o atributo HttpGetAttribute.

Note que nos dois exemplos a Microsoft assumiu essas regras e as asseguram durante a execução, ficando, de certa forma, transparente para nós. Se não houvesse isso, teríamos que configurar as mesmas coisas em 100% dos casos; com essas convenções predefinidas, precisamos apenas nos preocupar quando queremos algo diferente do padrão.

O ASP.NET MVC e Web API expõem tipos para que possamos customizar o framework de acordo com nossas necessidades. Até então, isso poderia ser realizado customizando alguns serviços de baixo nível (controller factory, por exemplo), mas o problema desta técnica é que estaremos tocando em outras atividades e que qualquer descuido poderemos ter problemas ao executar e, consequentemente, não atingirmos o objetivo esperado.

O ASP.NET 5 simplifica bastante essa customização expondo uma interface chamada IApplicationModelConvention. Ao implementar ela e acopla-la a execução, o ASP.NET identificará todos os controllers, ações, rotas e filtros candidatos a serem expostos (de acordo com a convenção predefinida) e entregará para analisarmos (através da classe ApplicationModel), e com isso, podemos customizar da forma que precisamos. Tudo isso através de uma API de mais fácil utilização, sem a necessidade de conhecer detalhes dos bastidores para implementar convenções que estão em um nível mais superficial.

Para exemplificar o uso, considere o controller abaixo, que possui duas ações e que retornam uma string. Como podemos perceber, o código foi escrito utilizando o português. Por convenção, o ASP.NET Web API assumirá que o nome dos membros (classe e métodos) serão os mesmos que serão expostos e utilizados pelo sistema de roteamento para montar os links de acesso à API e suas ações.

public class UtilitariosController : Controller
{
    [HttpGet]
    public string ExecutarTarefa()
    {
        return “…” ;
    }

    [HttpGet]
    public string EnviarEmail()
    {
        return “…”;
    }
}

A ideia é deixar o desenvolvedor livre para codificar no idioma desejado, e através do recurso de convenção, vamos estipular que o framework irá traduzir de acordo com a escolha realizada durante a sua configuração. Na ausência do idioma, a nomenclatura utilizada no código será exposta.

O que fazemos abaixo é no método Apply (único, exposto pela interface IApplicationModelConvention) percorremos os controllers encontrados pelo ASP.NET, procuramos pela chave no dicionário (que em um ambiente real, poderíamos estar recorrendo à algum outro repositório ao invés de hard-code), e por fim substituímos o nome do controller e da ação pelo encontrado no dicionário, baseado na cultura escolhida.

public class LocalizedApiNamingModelConvention : IApplicationModelConvention
{
    private string culture;

    private IDictionary<string, Tuple<string, string>> mapping =
        new Dictionary<string, Tuple<string, string>>();

    public LocalizedApiNamingModelConvention(string culture)
    {
        this.culture = culture;

        mapping.Add(
            “en-US.Utilitarios.ExecutarTarefa”,
            Tuple.Create(“Utilities”, “RunTask”));

        mapping.Add(
            “en-US.Utilitarios.EnviarEmail”,
            Tuple.Create(“Utilities”, “SendEmail”));
    }

    public void Apply(ApplicationModel application)
    {
        foreach (var c in application.Controllers)
        {
            var controllerName = c.ControllerName;

            foreach (var a in c.Actions)
            {
                var key = BuildKey(controllerName, a.ActionName);

                if (this.mapping.ContainsKey(key))
                {
                    var info = this.mapping[key];

                    c.ControllerName = info.Item1;
                    a.ActionName = info.Item2;
                }
            }
        }
    }

    private string BuildKey(string controllerName, string actionName)
    {
        return $”{culture}.{controllerName}.{actionName}”;
    }
}

Depois da classe criada, basta incluirmos na coleção chamada Conventions, e fazemos isso através do método AddMvcCore, conforme é mostrado no código abaixo:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore(o =>
        o.Conventions.Add(new LocalizedApiNamingModelConvention(“en-US”)));
}

Repare que criamos a classe e no construtor passamos a cultura en-US e ao rodar a aplicação, os links acessíveis serão /Utilities/RunTask e /Utilities/SendEmail. Os links em português retornarão o erro 404 do HTTP (Not Found).

Registrando Recursos para Eliminação

Apesar do ASP.NET Web API possui um mecanismo que nos permite estender e acoplar algum gerenciador de dependências, temos a possibilidade de registrar um determinado objeto para que ele seja descartado quando a requisição for completamente atendida.

É muito comum em aplicações Web criar um determinado objeto e querer mante-lo por toda a requisição, e em vários momentos e locais por onde a requisição trafega, podemos recuperar o referido objeto e reutiliza-lo para fazer alguma atividade complementar. Bancos de dados, loggers, entre outros, são exemplos de recursos que são úteis durante a vida da requisição dentro do pipeline onde ela está sendo processada.

A classe HttpRequestMessage possui uma propriedade chamada Properties (IDictionary<string, object>) que permite catalogarmos diversas informações inerentes à requisição, bem como recursos que podem ser (re)utilizados durante todas as etapas da execução. Uma vez adicionados, eles podem ser extraídos em qualquer momento no futuro que venha a precisar deles, mesmo que seja em outras camadas.

A dificuldade não é adicionar, mas saber o momento certo de descarta-lo. Simplesmente adicionar o recurso no dicionário não é suficiente para o ASP.NET Web API fazer a eliminação correta do mesmo. A Microsoft disponibilizou um método de extensão chamado RegisterForDispose, exposto pela classe HttpRequestMessageExtensions. Devemos utilizar este método para indicar explicitamente ao ASP.NET Web API que aquele objeto deve ser descartado no final da requisição. E, como era de se esperar, este método recebe objetos que implementam obrigatoriamente a interface IDisposable.

public class LoggerHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var logger = new TextLogger(@”C:TempLog.txt”);
        request.RegisterForDispose(logger);
        request.Properties.Add(“TextLogger”, logger);

        return base.SendAsync(request, cancellationToken).ContinueWith(tr =>
        {
            var response = tr.Result;

            logger.Write(“Request: ” + request.ToString()).ContinueWith(_ =>
                logger.Write(“Response: ” + response.ToString())).Wait();

            return response;
        }, cancellationToken);
    }
}

Curiosamente o método RegisterForDispose também recorre a propriedade Properties da classe HttpRequestMessage para acomodar os objetos que serão descartados quando a mesma também for removida. Caso encontre um momento mais oportuno para fazer o descarte, podemos recorrer ao método (também de extensão) chamado DisposeRequestResources para antecipar essa rotina, ou ainda, se preferir inspecionar os objetos marcados, pode-se utilizar o método GetResourcesForDisposal para isso.

Outro uso para o que vimos aqui é quando você opta por interceptar a criação do controller (através da implementação da interface IHttpControllerActivator), onde o mesmo pode necessitar de recursos em seu construtor e que sejam necessários serem marcados para descarte no término da requisição.

Conversão de Datas em Ações da API

Há momentos em que precisamos parametrizar as ações de controllers com outros tipos de dados que vão além de inteiros e strings, e sem ainda falar de tipos de complexos, o tipo DateTime pode ser uma necessidade muito comum em alguns casos. O problema é que este tipo de dado possui diversas formatações e sua validade varia de acordo com a cultura contra qual ele está sendo validado.

Sendo assim, podemos nos deparar com alguns problemas, onde apesar de ser uma data válida se considerarmos o formato brasileiro, ela pode ser considerada inválida pela API que utiliza o formato americano para tratar e vice versa. Mesmo que a cultura da aplicação esteja definida para a região correta, os componentes internos do ASP.NET Web API não são capazes de entender o formato da data que o cliente deve passar. Se considerarmos a data 20 de dezembro de 2015, 20/12/2015 é um formato válido para nós mas inválido para os Estados Unidos; já a data 12/20/2015 é o contrário.

Para especificar o formato exato para realizar a conversão, podemos criar um model binder específico para a data, e no interior do mesmo fazer a formatação desejada para todas as datas que são recebidas em parâmetros dos serviços. Abaixo está o exemplo de como poderíamos codificar este formatador:

public class CustomDateTimeModelBinder : IModelBinder
{
    private readonly string format;

    public CustomDateTimeModelBinder(string format)
    {
        this.format = format;
    }

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var date = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;

        if (string.IsNullOrWhiteSpace(date))
            return false;

        bindingContext
            .ModelState
            .SetModelValue(
                bindingContext.ModelName,
                bindingContext.ValueProvider.GetValue(bindingContext.ModelName));

        DateTime result = DateTime.MinValue;

        if (DateTime.TryParseExact(date, format, Thread.CurrentThread.CurrentCulture, DateTimeStyles.None, out result))
        {
            bindingContext.Model = result;
            return true;
        }

        bindingContext.ModelState.AddModelError(
            bindingContext.ModelName,
            string.Format(“”{0}” is invalid.”, bindingContext.ModelName));

        return false;
    }
}

Por fim, basta acoplá-lo à execução e indicar ao ASP.NET Web API que ele trata e valida o tipo DateTime. Para isso, basta utilizar a configuração do serviço para inclui-lo e as requisições que mandarem a data no formato especificado em seu construtor serão corretamente convertidas em DateTime respeitando o dia, mês e ano.

config.BindParameter(typeof(DateTime), new CustomDateTimeModelBinder(“dd/MM/yyyy”));