ASP.NET 4.5 C. Completo

Page 1


HANDLERS E MÓDULOS As handlers e os módulos são dois dos pontos de personalização e/ou extensão mais usados nas aplicações ASP.NET. O objetivo deste capítulo é apresentar algumas das principais particularidades associadas à utilização destes tipos de elementos.

17.1 INTRODUÇÃO ÀS HANDLERS Uma handler é uma classe capaz de satisfazer um pedido efetuado sobre um recurso controlado pela plataforma ASP.NET. Todas as componentes incluídas nesta categoria implementam uma de duas interfaces: IHttpHandler e IHttpAsyncHandler. Para além destas duas interfaces, existe ainda uma outra, designada por IHttpHandlerFactory, que é usada para identificar uma handler factory. Uma handler factory representa um tipo especial de handler – a principal função deste elemento é criar uma nova handler adequada ao contexto atual (existem vários elementos deste tipo na plataforma ASP.NET; por exemplo, as páginas ASPX são instanciadas através de uma handler deste tipo).

17.1.1 A INTERFACE IHTTPHANDLER A interface (IHttpHandler) é implementada por todas as classes capazes de efetuarem o tratamento de pedidos de forma síncrona. Nestes casos, a thread responsável por satisfazer o pedido atual será “bloqueada” até que a componente termine o processamento e retorne a resposta ao browser cliente. A interface IHttpHandler define apenas um método e uma propriedade: public interface IHttpHandler{ void ProcessRequest(HttpContext context); bool IsReusable { get; } } © FCA – Editora de Informática


638

ASP.NET 4.5

O método ProcessRequest é invocado pela plataforma sempre que é feito um pedido sobre um determinado recurso. Internamente, este método limita-se a gerar os vários eventos associados ao ciclo de vida de uma página (o Anexo A3 apresenta uma descrição pormenorizada deste método). A propriedade IsReusable indica-nos se uma instância pode ou não ser reutilizada para tratar vários pedidos. Quando esta propriedade devolve o valor true, então, a handler pode ser colocada numa pool de forma a satisfazer futuros pedidos. Note-se que a utilização de pooling é apenas indicada quando estamos perante handlers que demoram muito tempo a serem inicializadas. Quando a propriedade devolve o valor false, todos os pedidos sobre o recurso associado resultam na criação de uma nova instância desse tipo. Todas as páginas retornam o valor false em resposta à propriedade IsReusable.

17.1.2 A INTERFACE IHTTPASYNCHANDLER Estas handlers efetuam o processamento de forma assíncrona. Na prática, isto quer dizer que a thread responsável pelo tratamento do pedido é devolvida à pool após iniciar a operação assíncrona, ficando, assim, disponível para tratar outros pedidos HTTP que sejam realizados durante essa operação assíncrona. Ao ser notificada da conclusão da operação assíncrona, a plataforma recorre novamente a uma das threads da pool para reativar o processamento do pedido HTTP que tinha sido colocado em espera aquando do início da operação assíncrona para obter a resposta devolvida ao cliente. Nesta altura, importa referir que o uso destas handlers tende a contribuir apenas para um aumento da escalabilidade do site, não existindo (praticamente) ganhos no que diz respeito a melhorias do tempo de resposta associado à recuperação do conteúdo pedido. É por isso que o uso destas handlers deve ser realizado apenas quando elas efetuam operações longas de I/O (ex.: obtenção de dados provenientes de web services ou de base de dados). A interface IHttpAsyncHandler deriva da interface IHttpHandler, acrescentando-lhe dois novos métodos: public interface IHttpAsyncHandler : IHttpHandler{ IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData); void EndProcessRequest(IAsyncResult result); } © FCA – Editora de Informática


HANDLERS E MÓDULOS

639

A instanciação de uma handler fica sempre a cargo de um objeto do tipo HttpApplication. Após obter um objeto do tipo pretendido, a classe começa por verificar se a handler instanciada é síncrona ou assíncrona. Quando estamos perante uma handler assíncrona, a classe HttpApplication invoca o método BeginProcessRequest em vez do tradicional ProcessRequest. A implementação tradicional deste método consiste na criação de uma nova thread (que será responsável por tratar o pedido) e no retorno de um elemento do tipo IasyncResult, que permitirá obter o resultado final do processamento através da execução do método EndProcessRequest (na secção 17.3 analisamos detalhadamente a construção deste tipo de handlers).

17.1.3 PROCESSAMENTO ASSÍNCRONO DE PÁGINAS A plataforma permite o processamento assíncrono de páginas desde a versão 2.0. A utilização desta forma de processamento está dependente da utilização do atributo Async da diretiva @Page. O processamento de páginas assíncronas assenta no conceito de tarefa assíncrona. Uma tarefa assíncrona é representada por um método (que define um conjunto de instruções) que será executado de forma assíncrona. O registo das tarefas assíncronas é efetuado através da execução do método RegisterAsyncTask definido pela classe Page. A utilização de processamento assíncrono pressupõe a utilização do atributo Async (da diretiva @Page) com o valor true, cuja utilização implica a construção de uma classe final que implementa a interface IHttpAsyncHandler. Para além desse atributo, podemos ainda definir um valor de timeout que será usado para controlar a execução dos métodos assíncronos definidos (o timeout pode também ser definido através da propriedade AsyncTimeout da classe Page). A plataforma recorre à classe PageAsyncTask para guardar toda a informação necessária à execução de uma tarefa assíncrona: para além do método responsável pelo início assíncrono da operação, temos ainda de indicar o método que deve ser invocado aquando da conclusão da tarefa. Opcionalmente, podemos ainda indicar um método que será executado se ocorrer um timeout durante o processamento da tarefa. Para além desta informação, o construtor desta classe também permite passar informação adicional ao método que inicia todo o processo (o tipo deste parâmetro é object, pelo que é possível passar qualquer tipo de elemento). Devemos ainda decidir se as várias tarefas assíncronas existentes devem ou não © FCA – Editora de Informática


640

ASP.NET 4.5

ser executadas em série (a atribuição do valor true ao último parâmetro do construtor indica que os métodos devem ser invocados em paralelo). Após definirmos as nossas tarefas, devemos registá-las através da execução do método RegisterAsyncTask (esta operação de registo é normalmente efetuada durante o evento Load podendo também ser executada noutro evento anterior a este). Nas páginas assíncronas, todas as fases associadas ao ciclo de vida são executadas sequencialmente até ao evento PreRender. A partir desta altura, dá-se início ao processamento assíncrono das tarefas registadas e a thread utilizada até aí é libertada, regressando assim à pool de threads responsável por satisfazer os pedidos ASP.NET. A classe PageAsyncTaskManager efetua a gestão deste processo. Esta classe inicia o processamento de todas as tarefas assíncronas registadas previamente e a espera pela conclusão das mesmas. Em seguida, a página resume o seu percurso normal através do processamento das restantes fases associadas ao seu ciclo de vida. A ilustração deste tipo de páginas será feita através da criação de uma página assíncrona, que define duas tarefas assíncronas responsáveis por recuperarem dados existentes numa base de dados. O primeiro passo necessário é tornar a página numa handler assíncrona através da adição do atributo Async à diretiva @Page (cap17/ex1.aspx): <%@ Page Language="C#" Async="true"

%>

Em seguida, temos de registar a tarefa assíncrona até ao final do evento Load. Neste caso, optámos por definir as tarefas durante o evento PreInit da página para mostrar que estas podem ser registadas em qualquer evento que seja gerado antes do evento Load: protected override void OnPreInit( EventArgs e ) { base.OnPreInit( e ); PageAsyncTask task1 = new PageAsyncTask( new BeginEventHandler( BeginAsyncData ), new EndEventHandler( EndGetAsyncData ), new EndEventHandler( TimeoutOcurred ), null, true

//executar o metodo em paralelo

); PageAsyncTask task2 = new PageAsyncTask( © FCA – Editora de Informática


HANDLERS E MÓDULOS

641

new BeginEventHandler( BeginAsyncData2 ), new EndEventHandler( EndGetAsyncData2 ), new EndEventHandler( TimeoutOcurred2 ), null, true ); this.RegisterAsyncTask( task1 ); this.RegisterAsyncTask( task2 ); }

A criação de uma tarefa assíncrona envolve a definição de vários parâmetros: 1)

Um método responsável por iniciar a tarefa.

2)

Um método de callback que será invocado quando a tarefa assíncrona for concluída.

3)

Um método que será executado se ocorrer um timeout.

4)

Um valor genérico (portanto, do tipo object) que será passado ao método callback (neste caso, não necessitamos de passar qualquer valor, pelo que recorremos ao valor null).

5)

Um booleano que indica se a tarefa pode ser executada em paralelo com as restantes tarefas assíncronas.

Como referimos anteriormente, o registo da tarefa é feito através do método RegisterAsyncTask, que se encarrega de adicionar a tarefa a uma lista interna mantida pela página. Falta-nos apenas apresentar os métodos usados para iniciar e concluir as tarefas assíncronas. O excerto seguinte apresenta o código definido pelos métodos BeginAsyncData e EndGetAsyncData: private IAsyncResult BeginAsyncData(object src, EventArgs args, AsyncCallback cb, object state) { cnn = new SqlConnection( cnnString ); cmd = new SqlCommand( "select nome from alunos", cnn ); cnn.Open( ); Response.Write( "Método BeginAsync; Data" + DateTime.Now.ToString( ) + © FCA – Editora de Informática


642

ASP.NET 4.5 "; Thread Id:" + Thread.CurrentThread.ManagedThreadId.ToString()+ "<br />" ); IAsyncResult res = cmd.BeginExecuteReader( cb, state, CommandBehavior.CloseConnection ); //forcar sleep para demonstrar a utilizacao de varias //threads para processar os dados Thread.Sleep(2000); return res;

} private void EndGetAsyncData(IAsyncResult res) { using( var reader = cmd.EndExecuteReader( res ) ){ while ( reader.Read( ) ) { //nao vamos fazer nada... } } Response.Write( "Método EndGetAsync; Data" + DateTime.Now.ToString( ) + "; Thread Id:" + Thread.CurrentThread.ManagedThreadId.ToString() + "<br />" ); } private void TimeoutOcurred( IAsyncResult res ) { Response.Write( "<b>Timeout no método TimeoutOcurred</b>" ); cmd.Cancel(); cnn.Close(); } Note-se como os métodos anteriores não recorreram a qualquer primitiva de sincronização. Tal deve-se ao facto de as tarefas assíncronas efetuarem automaticamente a propagação do contexto e a sincronização de threads (Figura 17.1).

© FCA – Editora de Informática


HANDLERS E MÓDULOS

643

FIGURA 17.1 – Utilização de tarefas assíncronas

O método BeginAsyncData inicia a execução de um comando sobre uma base de dados SQL Server de forma assíncrona através da invocação do método BeginExecuteReader (este método retorna um valor do tipo IAsyncResult que é usado como tipo de retorno do nosso método BeginAsyncData). O método EndGetAsyncData é responsável por obter os resultados produzidos pela execução do comando através da invocação do método EndExecuteReader. Repare-se ainda como cancelamos o comando e encerramos a ligação a partir do método invocado em caso de timeout… Como referimos, a utilização de páginas assíncronas deve ser efetuada apenas quando estamos perante operações longas de I/O já que, nestes casos, a utilização de páginas síncronas pode levar ao bloqueio de todas as threads usadas para tratar pedidos ASP.NET. Nestes cenários, a utilização de páginas assíncronas faz com que as threads usadas para tratar os pedidos ASP.NET sejam retornadas à pool enquanto a operação “lenta” está a ser executada. Para além da utilização de tarefas assíncronas, podemos ainda configurar uma operação assíncrona através do método AddOnPreRenderCompleteAsync (exposto pela classe Page). O método recebe dois parâmetros: um delegate responsável pela execução de um método que inicia uma operação assíncrona e um delegate que será invocado quando essa operação assíncrona terminar. Se quisermos utilizar esta estratégia, então, teremos algum trabalho adicional uma vez que, por predefinição, é apenas registado um método que será invocado de forma assíncrona (se quisermos efetuar várias operações, então, esse método terá de dar início às várias tarefas assíncronas; o programador será ainda responsável por coordenar essas tarefas de forma a garantir que o delegate final só será executado após a conclusão de todas as tarefas assíncronas). Convém ter em atenção que a utilização desta estratégia não suporta a utilização de timeouts. Para além disso, não temos a propagação do contexto (portanto, HttpContext.Current retorna null). A sincronização de threads (obtida automaticamente quando usamos o método baseado nas tarefas assíncronas) também não está disponível © FCA – Editora de Informática


644

ASP.NET 4.5

quando utilizamos esta estratégia. Para além do método anterior, a plataforma introduz ainda uma outra estratégia para permitir a invocação de métodos de forma assíncrona designada por Event Based API. Neste caso, um método assíncrono expõe também um evento que será gerado para sinalizar o seu término. Por exemplo, se possuirmos um método síncrono designado por Teste que recebe um parâmetro X, então, a classe deve fornecer um método com uma assinatura semelhante à seguinte: void TesteAsync( X );

A classe deve ainda introduzir um evento designado por TesteCompleted do tipo TesteCompletedEventHandler. Este delegate utiliza um elemento do tipo TesteCompletedEventArgs como segundo parâmetro, que possui uma propriedade designada por Result que é usada para obter o resultado final. A utilização desta aproximação simplifica o trabalho do programador e resolve alguns dos problemas introduzidos pela estratégia anterior. Apesar de efetuar a propagação do contexto e de efetuar a sincronização das threads no que diz respeito ao acesso a dados partilhados, esta estratégia não nos permite definir um timeout e, atualmente, é apenas implementada pelos proxies gerados a partir de um Web Service.

17.2 CONSTRUÇÃO DE HANDLERS SÍNCRONAS Apesar de, na maior parte das vezes, o desenvolvimento ASPX consistir na construção de várias páginas e controlos, existem ocasiões em que a infraestrutura da página é desnecessária para a operação em causa. Nestes casos, pode ser necessário construirmos a nossa própria handler que contém apenas o código necessário à realização dessa operação. A construção de handlers síncronas implica a construção de uma classe que implementa a interface IHttpHandler. Para exemplificar a construção de uma handler síncrona, vamos construir uma classe capaz de obter uma imagem existente na tabela Categories da base de dados Northwind (SQL Server). O primeiro passo a dar consiste na definição dos requisitos associados à construção da classe que implementará a interface IHttpHandler: 

A handler não será reutilizada por vários pedidos.

Cada pedido tem de indicar o ID associado à imagem através de um parâmetro de query string designado por ID.

Tendo em atenção as restrições anteriores, a classe ImageGetter foi implementada da seguinte forma (app_code/ImageGetter.aspx):

© FCA – Editora de Informática


HANDLERS E MÓDULOS

645

namespace Livro { public class ImageGetter:IHttpHandler { public void ProcessRequest( HttpContext context ) { if( string.IsNullOrEmpty( context.Request.Params["ID"] ) ) { throw new ArgumentNullException( "O ID da imagem é necessário para obter a” + “ imagem a partir da base de dados" ); } int id = Convert.ToInt32( context.Request.Params["ID"] ); using( var cnn =new SqlConnection( WebConfigurationManager.ConnectionStrings["northwind"].→ ConnectionString)) { var cmd = new SqlCommand( "select Picture from Categories where CategoryID=@CategoryID", cnn ); cmd.Parameters.AddWithValue( "@CategoryID", id ); cnn.Open(); byte [] img = (byte [] )cmd.ExecuteScalar(); context.Response.ContentType = "image/jpeg"; context.Response.OutputStream.Write( img, 78, img.Length – 78); } } public bool IsReusable { get{ return false; } } } }

A implementação desta handler prima pela simplicidade: o método ProcessRequest limita-se a recuperar o ID da coluna, cuja imagem deve ser

devolvida e a estabelecer uma ligação à base de dados, de forma a recuperar a imagem sob a forma de um array de bytes. Em seguida, a handler recorre ao objeto Response existente no contexto para enviar a informação de volta ao browser. © FCA – Editora de Informática


646

ASP.NET 4.5

A base de dados Northwind armazena os campos de imagem como objetos OLE (provavelmente devido ao facto de a base de dados ter de ser originalmente construída em Access e depois importada para SQL Server). Devido a isso, não podemos simplesmente recorrer ao método BinaryWrite existente na classe HttpResponse, uma vez que os primeiros 78 bytes consistem num prefixo que deve ser eliminado se quisermos visualizar corretamente a imagem.

A utilização da handler anterior implica o estabelecimento de uma relação entre uma extensão e a nossa classe ImageGetter. Neste caso, vamos associar todos os pedidos efetuados sobre esta handler ao recurso virtual image.get. Para tal, temos de registar a handler na secção <handlers> existente no ficheiro de configuração: <handlers> <add name="imageGetter" path="image.get" type="Livro.ImageGetter" verb="*" requireAccess="Script"/> </handlers>

Esta instrução é suficiente para garantir que todos os pedidos efetuados para o recurso image.get servidos pelo IIS 7 são tratados pela nossa handler. Após efetuarmos estes passos, já podemos construir uma página que utiliza a nossa handler (cap17/ex2.aspx): <%@ Page Language="C#" %> <html> <body> <form id="form1" runat="server"> <img src="image.get?ID=1" > </form> </body> </html>

Como é possível verificar, limitamo-nos a configurar o atributo src do elemento <img>, de forma a que este invoque a nossa handler personalizada e passe o ID da categoria através do parâmetro GET ID. Ao receber o pedido, o servidor IIS consulta o URL e compara-o com a sua tabela interna de handlers (reencaminhando-a para a nossa classe ImageGetter). A implementação de uma handler personalizada apresenta algumas vantagens sobre a utilização da página neste tipo de operação, residindo a principal no facto de esta apenas conter o código necessário à obtenção da imagem. Se tivéssemos colocado o mesmo © FCA – Editora de Informática


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.