Pour les besoins d'un projet ASP.NET sur lequel je travaille actuellement, j'ai du mettre en place un système de sauvegarde d'images en base de données et d'affichage de celles-ci via un IHttpHandler.
Ce poste a pour objectif de vous détailler la procédure que j'ai mise en place pour réaliser cette tâche.
Dans la suite, je développerai avec Visual Studio 2008 Team System et j'utiliserai une base de données SQL Server 2005 Express.
Rappels
Qu'est-ce qu'un handler http ?
Un handler http est une classe, implémentant l'interface System.Web.IHttpHandler permettant de définir la manière dont doit être traitée une requête http, sur un chemin précis de notre application web.
L'interface IHttpHandler force la redéfinition de deux éléments :
- La méthode ProcessRequest : elle prend en paramètre une instance de la classe System.Web.HttpContext représentant le contexte http courant de notre application web, i.e. toutes les informations spécifiques au protocole http pour la requête courante. C'est dans cette méthode que nous allons définir le comportement particulier de notre requête et y effectuer le rendu.
- La propriété IsReusable : un booléen indiquant si une autre requête peut utiliser la même instance de notre handler http.
Un handler http personnalisé doit, pour être utilisé, être déclaré au préalable dans le fichier de configuration de notre site web. Nous verrons comment mettre cela en place dans la suite.
Création de la table de stockage des images dans la base
Nous avons besoin de stocker plusieurs informations en base de données afin de pouvoir afficher simplement les images depuis notre handler http :
- Une colonne de type « uniqueidentifer » , clé primaire de la table, permettant d'identifier de manière unique l'image (ImageID)
- Une colonne de type « varchar(80) » permettant de stocker le nom de l'image (ImageName)
- Une colonne de type « varchar(255) » permettant de stocker le mime type de l'image stockée (ImageContentType).
- Une colonne de type « image » représentant le flux binaire associé à notre image (ImageBinary).
Voilà le code SQL associé à la création de la table décrite ci-dessus :
CREATE TABLE [dbo].[ImageStorage](
[ImageID] [uniqueidentifier] NOT NULL,
[ImageName] [varchar](80) NOT NULL,
[ImageContentType] [varchar](255) NOT NULL,
[ImageBinary] [image] NOT NULL,
CONSTRAINT [PK_ImageStorage] PRIMARY KEY CLUSTERED
(
[ImageID] ASC
)
WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
)
ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
Vous devriez obtenir ceci :
Création du DataContext fortement typé associé à la base :
Pour mémoire, un DataContext fortement typé est le point d'entrée de LINQ to SQL pour notre application. Le designer de Visual Studio va nous permettre de réaliser le mapping objet-relationnel entre la base de données et notre application c# :
Pour cela, nous allons créer un projet de type ASP.NET Website basé sur le .NET Framework 3.5. Nous allons ajouter un item de type LINQ to SQL Classes au projet et y faire glisser la table ImageStorage décrite ci-dessus, depuis l'explorateur de serveur Visual Studio :
Intéressons nous à la propriété ImageBinary qui à été générée. Celle-ci est du type System.Data.Linq.Binary qui représente tout type binaire stocké en base de données. C'est via cette propriété que nous allons récupérer le flux de bytes de l'image.
Sauvegarde des images dans la base de données
Nous allons à présent réaliser une interface web simpliste nous permettant d'uploader des images et de les sauvegarder dans la base.
<form id="FormUpload" runat="server">
<div>
<asp:FileUpload ID="ImageUpload" runat="server" />
<asp:Button ID="btnUpload" runat="server"
OnClick="btnUpload_Click" Text="Upload" />
<asp:Label ID="LabelInfo" runat="server"></asp:Label>
</div>
</form>
Voici le code associé au clic sur le bouton « btnUpload » :
protected void btnUpload_Click(object sender, EventArgs e)
{
if (ImageUpload.PostedFile.FileName != string.Empty)
{
using (ArticleHandlerDataContext db = new ArticleHandlerDataContext())
{
try
{
ImageStorage image = new ImageStorage();
image.ImageID = Guid.NewGuid();
image.ImageName = ImageUpload.FileName;
image.ImageContentType = ImageUpload.PostedFile.ContentType;
image.ImageBinary = new System.Data.Linq.Binary(ImageUpload.FileBytes);
db.ImageStorages.InsertOnSubmit(image);
db.SubmitChanges();
this.LabelInfo.ForeColor = Color.Green;
this.LabelInfo.Text = "Upload ok.";
}
catch(Exception xcp)
{
this.LabelInfo.Text = xcp.Message;
this.LabelInfo.ForeColor = Color.Red;
}
}
}
}
Ici, nous nous assurons d'abord qu'un fichier a bien été choisi (il faudrait aussi s'assurer que ce soit bien une image en testant le ContentType). Ensuite nous instancions notre datacontext fortement typé dans un bloc « using »afin de communiquer avec la base de données. Nous créons ensuite notre ImageStorage, en affectant ses propriétés avec les valeurs adéquates, puis nous l'enregistrons dans la base de données.
Si vous testez l'interface, vous devriez obtenir ceci :
Et voilà l'entrée dans la base de données :
Création du handler http
Il ne nous reste plus qu'à créer la classe implémentant IHttpHandler pour afficher nos images :
/// <summary>
/// HttpHandler affichant les images stockées dans la base.
/// </summary>
public class ImageHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
HttpRequest l_Request = context.Request;
if (l_Request.QueryString["picture"] != null)
{
try
{
//récupération de l'id en queryString.
Guid imageID = new Guid(l_Request.QueryString["picture"]);
//instanciation du DataContext
using (ArticleHandlerDataContext db = new ArticleHandlerDataContext())
{
//récupération de l'image
ImageStorage image = db.ImageStorages.Single(i => i.ImageID == imageID);
//nom du fichier en header de la réponse
context.Response.AppendHeader("Content-Disposition", "inline; filename=\"" + image.ImageName + "\"");
//définition du ContentType de la réponse HTTP
context.Response.ContentType = image.ImageContentType;
//écriture du flux de bytes de l'image
context.Response.BinaryWrite(image.ImageBinary.ToArray());
}
}
catch (Exception xcp)//catch exception
{
context.Response.Write(xcp.Message);
}
}
}
#endregion
}
Nous devons à présent enregistrer notre handler http dans le fichier de configuration de notre application web afin qu'il soit utilisable. Cet enregistrement se fait dans la section « httpHandlers » de la section « system.web » :
<httpHandlers>
<remove verb="*" path="*.asmx"/>
<add verb="*" path="image.axd" type="ImageHandler" validate="false"/>
[…]
</httpHandlers>
Il ne nous reste plus qu'à tester notre http handler. Pour cela allez récupérer l'id d'une image envoyée dans la base de données et rendez vous à l'adresse :
http://nomdevotreprojet/image.axd?picture=id_image
Voici un aperçu du résultat :
A bientôt