Autore: Mario Ferrante
I Gestori HTTP costituiscono una delle caratteristiche fondamentali dell’architettura ASP.Net sin dalle sue prime versioni. Qualsiasi richiesta ad una pagina o ad una risorsa di un’applicazione ASP.net viene mappata e quindi servita da uno specifico Http Handler.Pagine .aspx, servizi web .asmx, user controls .ascx vengono tutti mappati attraverso gestori specifici inclusi nel framework quali Page Handler, Web Service Handler, ma è anche possibile costruire gestori personalizzati per aggiungere alla richiesta di una risorsa particolari funzionalità, come ad esempio elaborazioni delle immagini, creazione al volo di grafici, rendere inaccessibili alcuni file, creare estensioni personalizzate per le nostre pagine e così via.
In Asp.net i Gestori HTTP possono essere realizzati in due modi differerenti, Custom Handlers e Generic Handlers.:
-
I Custom Handlers sono blocchi di codice che entrano in azione ogni qualvolta viene chiamata una specifica risorsa come una pagina aspx, un immagine, o pagine con estensioni personalizzate. Questo tipo di gestore deve essere necessariamente registrato nel file di configurazione, sia esso machine.config o web.config, in base allo scope che noi vogliamo dare al gestore stesso, se a livello di macchina o di singola applicazione.
-
I Generic Handler invece sono file con estensione ashx che si comportano come una qualsiasi pagina aspx: devono essere richiamati direttamente, ad esempio tramite la Url di un Browser e sono soggetti alla compilazione “on-the-fly”. Inoltre i Generic Handler non necessitano di alcuna registrazione nel web.config
In questo articolo tratteremo proprio la costruzione di un Generic Handler ashx che ci permetterà di ridimensionare o tagliare al volo delle immagini attraverso dei parametri passati nella query string.
L’INTERFACCIA IHTTPHANDLER
Tutti i gestori http, compresi i Generic Handler sono classi che implementiamo l’interfaccia IHttpHandler, la quale espone un metodo ProcessRequest ed una proprietà di sola lettura IsReusable:
-
ProcessRequest accetta come parametro un oggetto HttpContext ed è il metodo nel quale viene implementata la logica di processo di una richiesta http.
-
IsReusable se True permette il riutilizzo della stessa istanza del gestore, altrimenti viene creata una nuova istanza ad ogni richiesta.
Di seguito l’interfaccia IHttpHandler che dobbiamo implementare:
interface IHttpHandler
{
void ProcessRequest(HttpContext context);
bool IsReusable { get;}
}
FACCIAMO PRATICA: IMPLEMENTAZIONE DI UN CUSTOM HANDLER
In questo esempio, vediamo come implementare un semplice gestore che mi dia un messaggio a video ogni volta che viene richiesta una determinata pagina aspx, ad esempio default.aspx.
Apriamo Visual Web Developer e creiamo un nuovo sito Web, se non è già presente aggiungiamo la cartella App_Code alla nostra applicazione.In App_Code aggiungiamo una classe che chiameremo SimpleHandler.csLa prima cosa da fare è quindi quella di implementare nella nostra classe l’interfaccia IHttpHandler:
public class SimpleHandler:IHttpHandler
{
}
Ora non ci resta che implementare il metodo ProcessRequest e la proprietà IsReusable, l’obbiettivo di questo Handler è quella di scrivere a video il classico HELLO WORLD ogni volta che venga richiesta la pagina Default.aspx. avremo:
public class SimpleHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.Write("<h2>Hello World</h2>");
}
public bool IsReusable
{
get {return false; }
}
}
REGISTRAZIONE NEL WEB.CONFIG
Una volta creato il nostro gestore, il secondo step è quello di registrare il gestore stesso nel web.config :
<httpHandlers>
<add path="Default.aspx" verb="*" type="SimpleHandler" />
</httpHandlers>
Possiamo compilare e quindi aprire la nostra pagina default.aspx, vedremo il nostro Hello World, senza aver implementato alcuna riga di codice nella pagina.
UN GENERIC HANDLER PER GESTIRE LE IMMAGINI
A questo punto costruiamo un Generic Handler per la gestione delle immagini, il cui obbiettivo è quello di
poter ridimensionare e tagliare un’immagine attraverso l’utilizzo di parametri passati al nostro file ashx in una query string.Aggiungiamo al nostro progetto un generic handler cliccando con il tasto destro sul nome del progetto e quindi su Add New Item e su Generic Handler, lo chiameremo ImageToolCs.ashx .
Come si può notare un file ashx, a parte la prima riga dichiarativa, non è altro che una classe che implementa IHttpHandler e quindi il metodo ProcessRequest e la proprietà IsReusable
<%@ WebHandler Language="C#" Class="ImageTool" %>
using System;
using System.Web;
public class ImageTool : IHttpHandler {
public void ProcessRequest (HttpContext context) {
}
public bool IsReusable {
get {
return false;
}
}
}
Attraverso una Querystring passeremo al gestore dei parametri che determineranno l’azione da compiere sull’immagine. I parametri sono:
-
imagepath: indica il percorso dell’immagine
-
width: quanto deve essere ridimensionata la larghezza dell’immagine, se il valore è 0 o width non è presente la larghezza verrà ridimensionata proporzionalmente all’altezza.
-
height: quanto deve essere ridimensionata l’altezza dell’immagine, se il valore è 0 o height non è presente l’altezza verrà ridimensionata proporzionalmente alla larghezza.
-
crop: se “1” indica al gestore che l’immagine deve essere tagliata.
-
sx: il punto di partenza sull’asse X per il taglio dell’immagine.
-
fx: il punto di arrivo sull’asse X per il taglio dell’immagine.
-
sy: il punto di partenza sull’asse Y per il taglio dell’immagine.
-
fy: il punto di arrivo sull’asse Y per il taglio dell’immagine.
Sx, Fx, Sy, Fy impostano i quattro punti del rettangolo che vogliamo ritagliare sull’immagine.
Aggiungiamo, quindi, al nostro handler l’implementazione del metodo ProcessRequest che valuta il percorso fisico dell’immagine passata attraverso la querystring imagepath e, in base al valore di crop, richiamerà il metodo Scale se l’immagine deve essere ridimensionata, ScaleAndCrop, invece, se l’immagine oltre ad essere ridimensionata deve anche essere tagliata.
public void ProcessRequest (HttpContext context) {
string img = context.Request.QueryString["imagepath"];
if ((context.Request.QueryString["Crop"] != null) && (context.Request.QueryString["Crop"] != "0"))
{
ScaleAndCrop(context, img, Width, Height, Sx, Sy, Fx, Fy);
}
else{
Scale(context, img, Width, Height);
}
// fermo il resto della risposta
context.Response.End();
}
Il metodo Scale si occupa di ridimensionare l’immagine in base ai valori di width e height e salva quindi l’immagine elaborata in uno stream di output:
public void Scale(HttpContext context, string pathImage, int width, int height)
{
// recuperiamo l’estensione dell’immagine
string contentType = pathImage.Substring(pathImage.LastIndexOf("."));
// creiamo un oggetto Bitmap che rappresenta la nostra immagine
using (Bitmap bmp = (Bitmap)(System.Drawing.Image.FromFile(context.Server.MapPath(pathImage))))
{
// controlliamo se width e height sono maggiori di 0
if (width > 0 || height > 0)
{
//se solo height=0, l'altezza viene ridotta proporzionalmente alla larghezza
if (height == 0)
height = Convert.ToInt32(((float)(width) / (float)(bmp.Width)) * bmp.Height);
//se solo height=0, l'altezza viene ridotta proporzionalmente alla larghezza
if (width == 0)
width = Convert.ToInt32(((float)(height) / (float)(bmp.Height)) * bmp.Width);
//Ora viene creata la thumbnail e salvata in un output di stream
using (Bitmap thumb = new Bitmap(bmp, width, height))
{
using (Graphics myGraphic = Graphics.FromImage(thumb))
{
myGraphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
myGraphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
myGraphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
Rectangle myrectangle = new Rectangle(0, 0, width, height);
myGraphic.DrawImage(bmp, myrectangle);
//salvo in uno stream di output
if (contentType.ToUpper().EndsWith(".JPEG") || contentType.ToUpper().EndsWith(".JPG"))
{
thumb.Save(context.Response.OutputStream, ImageFormat.Jpeg);
}
else
{
thumb.Save(context.Response.OutputStream, ImageFormat.Gif);
}
}
}
}
else
{
// Se width e height sono entrambi uguali a 0, l’immagine viene salvata nello stream cosi com’è
if (contentType.ToUpper().EndsWith(".JPEG") || contentType.ToUpper().EndsWith(".JPG"))
{
bmp.Save(context.Response.OutputStream, ImageFormat.Jpeg);
}
else
{
bmp.Save(context.Response.OutputStream, ImageFormat.Gif);
}
}
}
}
Infine realizziamo il metodo ScaleAndCrop il quale non solo si occupa di ridurre l’immagine, ma anche di tagliarla in base ai parametri passati nella query string. L’implementazione di questo metodo è simile a quella del precedente, analizziamo la parte in cui l’immagine viene tagliata:
using (Bitmap thumb = new Bitmap(width, height, bmp.PixelFormat))
{
using (Graphics mygraphic = Graphics.FromImage(thumb))
{
mygraphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
mygraphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
mygraphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
Rectangle myrect = new Rectangle(0, 0, width, height);
mygraphic.DrawImage(bmp, myrect);
// A questo punto viene costruito il Rettangolo di ritaglio
Rectangle recCrop = new Rectangle(sx, sy, (fx - sx), (fy - sy));
using (Bitmap bmpCroc = new Bitmap(recCrop.Width, recCrop.Height, bmp.PixelFormat))
using (Graphics mygr = Graphics.FromImage(bmpCroc))
{
Rectangle destRect = new Rectangle(0, 0, (fx - sx), (fy - sy));
mygr.DrawImage(thumb, destRect, recCrop.X, recCrop.Y, recCrop.Width, recCrop.Height, GraphicsUnit.Pixel);
if (contentType.ToUpper().EndsWith(".JPEG") || contentType.ToUpper().EndsWith(".JPG"))
{
bmpCroc.Save(context.Response.OutputStream, ImageFormat.Jpeg);
}
else
{
bmpCroc.Save(context.Response.OutputStream, ImageFormat.Gif);
}
}
}
}
E’ possibile analizzare il codice completo, anche in Visual Basic, nell’allegato all’articolo.
COME UTILIZZARE IL NOSTRO GENERIC HANDLER
A questo punto non ci resta che provare il nostro generic handler.
Digitiamo nella barra degli indirizzi di Internet Explorer o di un altro Browser:
http://localhost/generichandler/imagetool.ashx?imagepath=images/tramonto.jpg&width=300
In questo modo non facciamo altro che dire al nostro Handler di ridurre la larghezza dell’immagine Tramonto.jpg a 300px e di ridurre l’altezza in maniera proporzionale alla larghezza.
Se noi invece digitiamo:
http://localhost/generichandler/imagetool.ashx?imagepath=images/tramonto.jpg&width=300&crop=1&sx=50&fx=200&sy=50&fy=225
Il risultato che otteniamo è mostrato nella seguente immagine:

L’immagine non è solo stata ridotta ma anche tagliata in un rettangolo di 150px di larghezza e 175px di altezza.
E’ anche possibile chiamare il nostro gestore attraverso l’attributo ImageUrl di un controllo Image, oppure attraverso l’attributo src di img:
<asp:Image Id=”img” runat=”server” ImageUrl=”imagetool.ashx?imagepath=……&width=…&crop=1&…/>
<img src=”imagetool.ashx?imagepath=……&width=…&crop=1&sx=…&fx=…&sy=…&fy=…” />
In questo modo è possibile utilizzare il gestore all’interno di pagine aspx o anche di semplici pagine html.
CONCLUSIONI
Un Generic Handler è senza dubbio l’approccio più semplice per affrontare gli HTTP Handlers, il fatto che esso non debba essere registrato nel web.config e che non debba essere compilato in un assembly rende il suo utilizzo molto più immediato rispetto ad un Custom Handler.
Però gli stessi vantaggi di utilizzo di questo tipo di Handler possono diventare svantaggi ogni qual volta si ha bisogno di creare gestori che funzionano a livello di macchina e non di singola applicazione, oppure ho la necessita di creare gestori che controllino pagine con estensioni personalizzate e che quindi richiedono di essere mappate su IIS. In tutti questi casi l’utilizzo dei Custom Handlers diventa inevitabile.