Desde a versão 1.x do ASP.NET podemos facilmente criar um handler para permitir a execução de algum código. O idéia por detrás deste handler, é justamente servir como alvo para alguma requisição. Um exemplo muito típico é a extração de arquivos do banco de dados, arquivos do disco, geração automática de imagens, etc..
Havia alguns detalhes necessários na versão 1.x que, se não realizados, não funcionava adequadamente. A partir da versão 2.0, a Microsoft incluiu um novo tipo de arquivo, com extensão ASHX, chamado de Generic Handler. Esse arquivo traz uma classe que, por padrão, implementa a interface IHttpHandler, cabendo aos desenvolvedores implementar o método ProcessRequest para tratar o pedido.
O problema é que o processamento deste handler estará ocupando uma thread do ThreadPool que, por sua vez, serve as requisições para as páginas da aplicação. Caso o código a ser realizado dentro deste handler for um processo custoso, podemos comprometer as outras requisições que nada tem a ver com ele (este artigo explica mais detalhes sobre este comportamento).
Para permitir que o handler também faça o uso do processamento assíncrono, podemos fazer com que o mesmo implemente a interface IHttpAsyncHandler e, ao invés de implementar o método ProcessRequest, devemos implementar os métodos BeginProcessRequest e EndProcessRequest. O exemplo abaixo utiliza os métodos (BeginRead e EndRead) para leitura assíncrona de um arquivo do disco:
public class ImageAsyncHandler : IHttpAsyncHandler
{
private struct InnerState : IDisposable
{
public HttpContext HttpContext;
public FileStream Stream;
public void Dispose()
{
if (this.Stream != null)
this.Stream.Dispose();
}
}
public bool IsReusable
{
get
{
return false;
}
}
public IAsyncResult BeginProcessRequest(HttpContext context,
AsyncCallback cb, object extraData)
{
string fileName =
context.Server.MapPath(context.Request.QueryString[“FileName”]);
FileStream fs = new FileStream(fileName, FileMode.Open);
int length = (int)fs.Length;
return fs.BeginRead(new byte[length], 0, length, cb,
new InnerState()
{
HttpContext = context
, Stream = fs
});
}
public void ProcessRequest(HttpContext context) { }
public void EndProcessRequest(IAsyncResult result)
{
using (InnerState state = (InnerState)result.AsyncState)
{
int length = state.Stream.EndRead(result);
state.Stream.Position = 0;
byte[] temp = new byte[length];
state.Stream.Read(temp, 0, length);
state.HttpContext.Response.OutputStream.Write(temp, 0, length);
}
}
}
Esse processo não mudará nada o resultado final do handler, mas trabalhará de uma forma muito mais otimizada, garantido que threads criadas para servir a aplicação não fiquem ocupadas executando um processamento mais custoso. Caso a imagem ou os dados venham de um banco de dados, podemos combinar essa técnica com os métodos assíncronos do ADO.NET 2.0.