Quando a requisição chega em um determinado serviço ela tem como alvo executar uma determinada tarefa e devolver um resultado. Antes da requisição chegar ao seu destino, ela passa por algumas etapas, e depois que a tarefa é de fato executada, outras etapas também são executadas antes de devolver a resposta para o cliente.
A manutenção de estado de objetos muitas vezes se faz necessária, pois ele servirá como uma espécie de contexto que viverá do início ao fim da requisição. Um exemplo comum disso é quando temos um objeto que gerencia uma transação (Unit of Work), e com ele seria possível envolver tudo o que acontece na requisição em um mesmo contexto transacional, pois se no fim do processamento alguma falha ocorrer, será possível reverter tudo o que fora realizado até ali.
Como sabemos, a ação dentro do controller de fato é a tarefa que queremos executar, mas o que ocorre antes e depois (geralmente são atividades de cross-cutting) também pode ser envolvido para contextualizar todo o processo. Este artigo exemplifica em como implementar e gerenciar isso no ASP.NET Web API. Para o exemplo, vamos ter um recurso que interceptará vários estágios da requisição, e logará todos os passos por onde ela passou. A interface IRequestTracking servirá como base para qualquer tipo de rastreador de requisições. A classe que está a seguir (MemoryRequestTracking) é uma implementação dela, que basicamente armazena os passos em uma coleção e persiste no Trace.
[InheritedExport]
public interface IRequestTracking
{
Guid RequestId { get; }
IEnumerable<string> Steps { get; }
void AddStep(string message);
void Flush();
}
public class MemoryRequestTracking : IRequestTracking
{
private readonly IList<string> steps;
public MemoryRequestTracking()
{
this.RequestId = Guid.NewGuid();
this.steps = new List<string>();
}
public void AddStep(string message)
{
this.steps.Add(
string.Format(“{0:dd/MM/yyyy HH:mm:ss} – Id: {1} – {2}”,
DateTime.Now, this.RequestId, message));
}
public Guid RequestId { get; private set; }
public IEnumerable<string> Steps { get { return this.steps; } }
public void Flush()
{
foreach (var step in this.Steps)
Trace.WriteLine(step);
}
}
O ASP.NET Web API já traz nativamente uma espécie de repositório de dependências, que durante o runtime, a medida em que recursos vão sendo solicitados, esse repositório é consultado para resolver a(s) dependência(s), retornando uma implementação concreta do recurso solicitado. Felizmente podemos customizar esse repositório e utilizar algum container de injeção de dependências (DI) de sua preferência para auxiliar no gerenciamento e na criação das instâncias.
A customização resume em se criar uma classe que implemente a interface IDependencyResolver, e através dos métodos autoexplicativos, retornamos os recursos solicitados. É no interior desta classe que, eventualmente, podemos utilizar um container de DI, e para este exemplo estou utilizando o MEF (Managed Extensibility Framework).
public class MefResolver : IDependencyResolver
{
private readonly CompositionContainer container;
public MefResolver()
: this(new CompositionContainer(
new AssemblyCatalog(Assembly.GetExecutingAssembly()))) { }
public MefResolver(CompositionContainer container) { this.container = container; }
public IDependencyScope BeginScope() { return new MefResolver(); }
public object GetService(Type serviceType)
{
return GetServices(serviceType).FirstOrDefault();
}
public IEnumerable<object> GetServices(Type serviceType)
{
var services = this.container.GetExports(serviceType, null, null);
return !services.Any() ? Enumerable.Empty<object>() : services.Select(s => s.Value);
}
public void Dispose() { this.container.Dispose(); }
}
Basicamente a classe acima recebe em seu construtor o container que servirá como “base de dados” das dependências e resolverá as solicitações procurando por implementações dentro do próprio assembly que está em execução. Só que a classe por si só não funcionará, pois precisamos acoplá-la à execução, e para isso, devemos configurar definer a instância dela na propriedade DependencyResolver exposta pela classe HttpConfiguration, assim como é mostrado abaixo:
public static void Register(HttpConfiguration config)
{
config.DependencyResolver = new MefResolver();
//outras configurações
}
Agora que toda a configuração já está realizada, precisamos começar a adicionar os passos nos locais que desejarmos interceptar. Como falei anteriormente, vamos querer acessar o rastreador em várias etapas diferentes, e felizmente, o ASP.NET Web API e seu container de DI são capazes de criar e disponibilizar a instância quando precisarmos, e isso quer dizer que poderemos acessar esses recursos não só no interior do controller, mas também em filtros, handlers, formatters, etc.
O gestor de dependência que temos (MefResolver) é construído no ínicio da requisição e é adicionado às propriedades da classe HttpRequestMessage, que nada mais é do que um dicionário de dados e mantém uma relação de objetos que são utilizados durante a requisição. Como o gestor de dependência é criado para cada requisição, então os objetos que são criados a partir dele também serão mantidos.
O primeiro lugar que vamos acessar é dentro de um filtro customizado. Note que a classe base ActionFilterAttribute fornece métodos para interceptar o antes e depois da ação executada.
public class RequestTrackingFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
((IRequestTracking)actionContext
.Request
.GetDependencyScope()
.GetService(typeof(IRequestTracking)))
.AddStep(“OnActionExecuting”);
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
((IRequestTracking)actionExecutedContext
.Request
.GetDependencyScope()
.GetService(typeof(IRequestTracking)))
.AddStep(“OnActionExecuted”);
base.OnActionExecuted(actionExecutedContext);
}
}
Graças à um método de extensão chamado GetDependencyScope atribuído a classe HttpRequestMessage, é fácil chegarmos à esse gestor e solicitar o recurso que queremos. Para expressar o recurso que queremos, basta informar a interface IRequestTracking, que ele devolverá a instância criada ou criará uma nova caso ela ainda não exista. Depois basta fazer a conversão explícita (casting) e acessar os seus membros.
Já dentro do controller, a forma de chegar até este recurso é bem semelhante. Note que no código abaixo temos o rastreador declarado como um campo na classe, e como ele está decorado com o atributo ImportAttribute (do MEF), o container resolve a dependência e injeta a instância nesta classe, neste campo, assim que ela for construída. Com isso, basta utilizar o mesmo no interior da ação/controller.
[Export]
public class TesteController : ApiController
{
[Import]
private IRequestTracking tracking;
[HttpGet]
[RequestTrackingFilter]
public string Ping(string valor)
{
tracking.AddStep(string.Format(“TesteController.Ping({0})”, valor));
return valor + ” ping”;
}
}
É importante notar que o atributo criado anteriormente é decorado no méotdo Ping, e é a presença dele que faz com o que o runtime do ASP.NET o execute.
Além destas opções, podemos também acessar e utilizar este rastreador, por exemplo, dentro de um message handler. Além de utilizar para também adicionar mais um passo ao rastreador, é também o momento de invocar o método Flush para que o conteúdo armazenado até o momento seja definitivamente adicionado no arquivo de trace, previamente configurado. Como podemos notar no código abaixo, fazemos todo este trabalho depois que a requisição foi processada por todos os componentes (de mais baixo nível), incluindo a ação que está dentro do controller. O flush é feito momentos antes de retornar a resposta para o cliente.
public class FlushingRequestTrackingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return await base.SendAsync(request, cancellationToken).ContinueWith(r =>
{
var tracking =
((IRequestTracking)request
.GetDependencyScope()
.GetService(typeof(IRequestTracking)));
tracking.AddStep(“FRTH.SendAsync”);
tracking.Flush();
return r.Result;
});
}
}
E, ao rodar e visualizarmos o arquivo de tracing, teremos:
13/11/2014 15:44:34 – Id: 724b2a97-3bcf-428b-a65e-5ec582bb1f70 – OnActionExecuting
13/11/2014 15:44:34 – Id: 724b2a97-3bcf-428b-a65e-5ec582bb1f70 – TesteController.Ping(teste)
13/11/2014 15:44:34 – Id: 724b2a97-3bcf-428b-a65e-5ec582bb1f70 – OnActionExecuted
13/11/2014 15:44:34 – Id: 724b2a97-3bcf-428b-a65e-5ec582bb1f70 – FRTH.SendAsync
Importante: Apesar de tentador, é necessário tomar cuidado ao acessar o gestor de dependências através da propriedade fornecida pela GlobalConfiguration.Configuration.DependencyResolver. O problema é que esta propriedade sempre retornará uma nova instância do gestor (no nosso caso, do MefResolver) e não teremos os objetos sendo mantidos por toda a requisição.