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 IHttpRequestFeature e IHttpResponseFeature, 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();
}