Comment faire un handler d’image en asp.net mvc

 

ASP.NET MVC

Lors de la création d’un site web un peu compliqué ou par exemple les images se trouvent sous forme de blob dans une base de données, ou les fichiers images existent à differents endroits, et pas mal d’autre cas encore, il faut utiliser un handler d’image, qui permet d’intercepter la requete de de produire un traitement avant de rendre l’image demandée.

Avec asp.net version webforms il fallait declarer un handler, souvent une url de la forme http://www.monsite.com/image.ashx?fileName=image.gif, ou plus seo compliant http://www.monsite.com/image.ashx/image.gif (malheureusement cette forme d’url ne fonctionnant pas avec le server web de dev cassini).

Avec asp.net mvc l’idée est de faire passer toutes les requetes via le “ControllerFactory”, il faut ajouter une route de la forme :

routes.MapRoute(
"Images"
,"images/{*fileName}"
, new { controller = "Images", action = "Index" }
);

L’url peut etre de la forme http://www.monsite.com/images/image.gif, attention il ne faut pas que le repertoire images existe à la racine du site.


 


A savoir, {*imageFilename} permet d’ajouter ce que l’on veut comme nom de fichier et par exemple /folder1/folder2/image.gif


 


Le controller est codé de la manière suivante :


 



public class ImagesController : Controller
{
public ActionResult Index(string fileName)
{
fileName = System.IO.Path.Combine(@"c:\images", fileName);
var mimeType = "image/gif";
return this.File(fileName, mimeType);
}
}



Pour ne pas compliquer le discours, je suis parti du principe que les images se trouvent dans le repertoire c:\images et qu’il n’y a que des fichiers gif, l’interception est la combinaison du nom de fichier et son chemin.



Le problème ici est qu’il manque le type mime, voici ci dessous un petit dictionnaire qui permet de retrouver un mimeType via une extension ou vice/versa.




public static Dictionary<string, string> MIMETypeDictionary
= new Dictionary<string, string>()
{
{"323", "text/h323"},
{"3gp", "video/3gpp"},
{"3gpp", "video/3gpp"},
{"acp", "audio/x-mei-aac"},
{"act", "text/xml"},
{"actproj", "text/plain"},
{"ade", "application/msaccess"},
{"adp", "application/msaccess"},
{"ai", "application/postscript"},
{"aif", "audio/aiff"},
{"aifc", "audio/aiff"},
{"aiff", "audio/aiff"},
{"asf", "video/x-ms-asf"},
{"asm", "text/plain"},
{"asx", "video/x-ms-asf"},
{"au", "audio/basic"},
{"avi", "video/avi"},
{"bmp", "image/bmp"},
{"bwp", "application/x-bwpreview"},
{"c", "text/plain"},
{"cat", "application/vnd.ms-pki.seccat"},
{"cc", "text/plain"},
{"cdf", "application/x-cdf"},
{"cer", "application/x-x509-ca-cert"},
{"cod", "text/plain"},
{"cpp", "text/plain"},
{"crl", "application/pkix-crl"},
{"crt", "application/x-x509-ca-cert"},
{"cs", "text/plain"},
{"css", "text/css"},
{"csv", "application/vnd.ms-excel"},
{"cxx", "text/plain"},
{"dbs", "text/plain"},
{"def", "text/plain"},
{"der", "application/x-x509-ca-cert"},
{"dib", "image/bmp"},
{"dif", "video/x-dv"},
{"dll", "application/x-msdownload"},
{"doc", "application/msword"},
{"dot", "application/msword"},
{"dsp", "text/plain"},
{"dsw", "text/plain"},
{"dv", "video/x-dv"},
{"edn", "application/vnd.adobe.edn"},
{"eml", "message/rfc822"},
{"eps", "application/postscript"},
{"etd", "application/x-ebx"},
{"etp", "text/plain"},
{"exe", "application/x-msdownload"},
{"ext", "text/plain"},
{"fdf", "application/vnd.fdf"},
{"fif", "application/fractals"},
{"fky", "text/plain"},
{"gif", "image/gif"},
{"gz", "application/x-gzip"},
{"h", "text/plain"},
{"hpp", "text/plain"},
{"hqx", "application/mac-binhex40"},
{"hta", "application/hta"},
{"htc", "text/x-component"},
{"htm", "text/html"},
{"html", "text/html"},
{"htt", "text/webviewhtml"},
{"hxx", "text/plain"},
{"i", "text/plain"},
{"iad", "application/x-iad"},
{"ico", "image/x-icon"},
{"ics", "text/calendar"},
{"idl", "text/plain"},
{"iii", "application/x-iphone"},
{"inc", "text/plain"},
{"infopathxml", "application/ms-infopath.xml"},
{"inl", "text/plain"},
{"ins", "application/x-internet-signup"},
{"iqy", "text/x-ms-iqy"},
{"isp", "application/x-internet-signup"},
{"java", "text/java"},
{"jnlp", "application/x-java-jnlp-file"},
{"jpg", "image/jpeg"},
{"jpe", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"jfif", "image/jpeg"},
{"jsl", "text/plain"},
{"kci", "text/plain"},
{"la1", "audio/x-liquid-file"},
{"lar", "application/x-laplayer-reg"},
{"latex", "application/x-latex"},
{"lavs", "audio/x-liquid-secure"},
{"lgn", "text/plain"},
{"lmsff", "audio/x-la-lms"},
{"lqt", "audio/x-la-lqt"},
{"lst", "text/plain"},
{"m1v", "video/mpeg"},
{"m3u", "audio/mpegurl"},
{"m4e", "video/mpeg4"},
{"MAC", "image/x-macpaint"},
{"mak", "text/plain"},
{"man", "application/x-troff-man"},
{"map", "text/plain"},
{"mda", "application/msaccess"},
{"mdb", "application/msaccess"},
{"mde", "application/msaccess"},
{"mdi", "image/vnd.ms-modi"},
{"mfp", "application/x-shockwave-flash"},
{"mht", "message/rfc822"},
{"mhtml", "message/rfc822"},
{"mid", "audio/mid"},
{"midi", "audio/mid"},
{"mk", "text/plain"},
{"mnd", "audio/x-musicnet-download"},
{"mns", "audio/x-musicnet-stream"},
{"MP1", "audio/mp1"},
{"mp2", "video/mpeg"},
{"mp2v", "video/mpeg"},
{"mp3", "audio/mpeg"},
{"mp4", "video/mp4"},
{"mpa", "video/mpeg"},
{"mpe", "video/mpeg"},
{"mpeg", "video/mpeg"},
{"mpf", "application/vnd.ms-mediapackage"},
{"mpg", "video/mpeg"},
{"mpg4", "video/mp4"},
{"mpga", "audio/rn-mpeg"},
{"mpv2", "video/mpeg"},
{"NMW", "application/nmwb"},
{"nws", "message/rfc822"},
{"odc", "text/x-ms-odc"},
{"odh", "text/plain"},
{"odl", "text/plain"},
{"p10", "application/pkcs10"},
{"p12", "application/x-pkcs12"},
{"p7b", "application/x-pkcs7-certificates"},
{"p7c", "application/pkcs7-mime"},
{"p7m", "application/pkcs7-mime"},
{"p7r", "application/x-pkcs7-certreqresp"},
{"p7s", "application/pkcs7-signature"},
{"PCT", "image/pict"},
{"pdf", "application/pdf"},
{"pdx", "application/vnd.adobe.pdx"},
{"pfx", "application/x-pkcs12"},
{"pic", "image/pict"},
{"PICT", "image/pict"},
{"pko", "application/vnd.ms-pki.pko"},
{"png", "image/png"},
{"pnt", "image/x-macpaint"},
{"pntg", "image/x-macpaint"},
{"pot", "application/vnd.ms-powerpoint"},
{"ppa", "application/vnd.ms-powerpoint"},
{"pps", "application/vnd.ms-powerpoint"},
{"ppt", "application/vnd.ms-powerpoint"},
{"prc", "text/plain"},
{"prf", "application/pics-rules"},
{"ps", "application/postscript"},
{"pub", "application/vnd.ms-publisher"},
{"pwz", "application/vnd.ms-powerpoint"},
{"qt", "video/quicktime"},
{"qti", "image/x-quicktime"},
{"qtif", "image/x-quicktime"},
{"qtl", "application/x-quicktimeplayer"},
{"qup", "application/x-quicktimeupdater"},
{"r1m", "application/vnd.rn-recording"},
{"r3t", "text/vnd.rn-realtext3d"},
{"RA", "audio/vnd.rn-realaudio"},
{"RAM", "audio/x-pn-realaudio"},
{"rat", "application/rat-file"},
{"rc", "text/plain"},
{"rc2", "text/plain"},
{"rct", "text/plain"},
{"rec", "application/vnd.rn-recording"},
{"rgs", "text/plain"},
{"rjs", "application/vnd.rn-realsystem-rjs"},
{"rjt", "application/vnd.rn-realsystem-rjt"},
{"RM", "application/vnd.rn-realmedia"},
{"rmf", "application/vnd.adobe.rmf"},
{"rmi", "audio/mid"},
{"RMJ", "application/vnd.rn-realsystem-rmj"},
{"RMM", "audio/x-pn-realaudio"},
{"rms", "application/vnd.rn-realmedia-secure"},
{"rmvb", "application/vnd.rn-realmedia-vbr"},
{"RMX", "application/vnd.rn-realsystem-rmx"},
{"RNX", "application/vnd.rn-realplayer"},
{"rp", "image/vnd.rn-realpix"},
{"RPM", "audio/x-pn-realaudio-plugin"},
{"rqy", "text/x-ms-rqy"},
{"rsml", "application/vnd.rn-rsml"},
{"rt", "text/vnd.rn-realtext"},
{"rtf", "application/msword"},
{"rul", "text/plain"},
{"RV", "video/vnd.rn-realvideo"},
{"s", "text/plain"},
{"sc2", "application/schdpl32"},
{"scd", "application/schdpl32"},
{"sch", "application/schdpl32"},
{"sct", "text/scriptlet"},
{"sd2", "audio/x-sd2"},
{"sdp", "application/sdp"},
{"sit", "application/x-stuffit"},
{"slk", "application/vnd.ms-excel"},
{"sln", "application/octet-stream"},
{"SMI", "application/smil"},
{"smil", "application/smil"},
{"snd", "audio/basic"},
{"snp", "application/msaccess"},
{"spc", "application/x-pkcs7-certificates"},
{"spl", "application/futuresplash"},
{"sql", "text/plain"},
{"srf", "text/plain"},
{"ssm", "application/streamingmedia"},
{"sst", "application/vnd.ms-pki.certstore"},
{"stl", "application/vnd.ms-pki.stl"},
{"swf", "application/x-shockwave-flash"},
{"tab", "text/plain"},
{"tar", "application/x-tar"},
{"tdl", "text/xml"},
{"tgz", "application/x-compressed"},
{"tif", "image/tiff"},
{"tiff", "image/tiff"},
{"tlh", "text/plain"},
{"tli", "text/plain"},
{"torrent", "application/x-bittorrent"},
{"trg", "text/plain"},
{"txt", "text/plain"},
{"udf", "text/plain"},
{"udt", "text/plain"},
{"uls", "text/iuls"},
{"user", "text/plain"},
{"usr", "text/plain"},
{"vb", "text/plain"},
{"vcf", "text/x-vcard"},
{"vcproj", "text/plain"},
{"viw", "text/plain"},
{"vpg", "application/x-vpeg005"},
{"vspscc", "text/plain"},
{"vsscc", "text/plain"},
{"vssscc", "text/plain"},
{"wav", "audio/wav"},
{"wax", "audio/x-ms-wax"},
{"wbk", "application/msword"},
{"wiz", "application/msword"},
{"wm", "video/x-ms-wm"},
{"wma", "audio/x-ms-wma"},
{"wmd", "application/x-ms-wmd"},
{"wmv", "video/x-ms-wmv"},
{"wmx", "video/x-ms-wmx"},
{"wmz", "application/x-ms-wmz"},
{"wpl", "application/vnd.ms-wpl"},
{"wprj", "application/webzip"},
{"wsc", "text/scriptlet"},
{"wvx", "video/x-ms-wvx"},
{"XBM", "image/x-xbitmap"},
{"xdp", "application/vnd.adobe.xdp+xml"},
{"xfd", "application/vnd.adobe.xfd+xml"},
{"xfdf", "application/vnd.adobe.xfdf"},
{"xla", "application/vnd.ms-excel"},
{"xlb", "application/vnd.ms-excel"},
{"xlc", "application/vnd.ms-excel"},
{"xld", "application/vnd.ms-excel"},
{"xlk", "application/vnd.ms-excel"},
{"xll", "application/vnd.ms-excel"},
{"xlm", "application/vnd.ms-excel"},
{"xls", "application/vnd.ms-excel"},
{"xlt", "application/vnd.ms-excel"},
{"xlv", "application/vnd.ms-excel"},
{"xlw", "application/vnd.ms-excel"},
{"xml", "text/xml"},
{"xpl", "audio/scpls"},
{"xsl", "text/xml"},
{"z", "application/x-compress"},
{"zip", "application/x-zip-compressed"}
};





Le code devient alors :




public ActionResult Index(string fileName)
{
fileName = System.IO.Path.Combine(@"c:\images", fileName);
var ext = System.IO.Path.GetExtension(fileName);
var mime = MIMETypeDictionary.FirstOrDefault(i => i.Key.Equals(ext, StringComparison.InvariantCultureIgnoreCase));
return this.File(fileName, mime.Value);
}

A noter il est possible de renvoyer un tableau de byte à la place d’un fichier, d’autre part si IIS6 est utilisé pour héberger le site il faut soit declarer chaque extension d’application possible par type d’image, ou ajouter un wildcard de la manière suivante :


 


image

8 commentaires:

Da Scritch a dit…

Pas mal, mais bourrées d'erreurs structurelles :
1- Le PNG est plus efficace que le GIF (sauf pour l'animation, je te le concède). Inconvénient pour toi : pas de brevets dessus.
2- Typiquement Windowsien, l'idée de lier type-mime avec l'extension, ce qui est une belle impasse. Tu risques notamment d'oublier une délcaration dans ta liste longue comme un jour sans pain. Avec Apache, on a une lookup ou sinon mieux, la bibliothèque fileinfo. Si tu stockes en base, autant y mettre le typemime.
3- il te manque une info qui est quand même essentielle : tu déclares pas la taille de ton document dans ton header.
4- Tu oublies de tester le type de requête. Si tu es en HEAD, inutile de répondre comme si tu es en GET. Une bonne partie de mon traffic de bots ne fait que des requêtes HEAD lors de leur x-ième visite. C'est aussi le cas pour des solutions de proxys d'entreprises. Lire en continu ses logs apache dans un terminal t'amèneras vers l'Illumination.
5- si tu stockes dans un répertoire, autant qu'elle soit publique dans l'arbo public du site. Tu gagnes énormément en traitement. Voire même mieux, utiliser les liens symboliques conditionnels.

Sympa ton code en Visual Basic. Tu veux pas passer à du vrai langage d'adulte sur des infrastructures sérieuses ?

Marc Chouteau a dit…

Le but pour moi n'est pas de demontrer que png est mieux que le gif, c'est surtout d'indiquer que le nouveau modele mvc permet de faire du handling très facilement, naturellement.

Bien sur c'est du code de demo, il manque pas mal de controles.

jacques massa a dit…

Da Scritch j'aime bien ton esprit critique et tes commentaires.
Cela démontre d'une part que tu attends vraiment tout du web et des blogs sans même réfléchir bizarre pour un développeur ??? et d'autre part que tu n'as aucun idée de la réalité du marché concernant Visual Basic :
Je te met au défis de trouver un langage moderne ( j'exclue C et Fortran ) y compris Java sur lequel repose dans le monde autant d'applications Professionelles. Bien sur tu as le droit d'aimer ou pas, mais pas d'insulter tous les millions de développeurs qui utilisent VB c'est un total manque de respect :( Bon d'un autre coté y a pas dans Apache un module respect ou humilité.
Concernant Marc en particulier, j'ai travaillé avec lui pendant plusieurs années et je peux te garantir que comme développeur il est au top!
Qd on fait des commentaires, on a le droit d'être critique et de faire des remarques mais pas d'être arrogant et cela qu'on vienne du monde libre ou de MS!
Hope this Helps.
Jacques Massa

Da Scritch a dit…

Effectivement, j'y ai été un peu fort. Mais il se trouve que je suis ancien collègue et (j'espère) bon camarade de Marc.

Son code est intéressant sur la flexibilité objet/MVC (j'en fais de même de mon côté mais avec d'autres technos), mais sa démonstration était malheureusement un nid d'erreurs structurelles. Le tableau de type-mime est à mon sens un non-sens matériel, à moins qu'il n'aie usé d'un stratagème pour caser un maximum de mots clés (petit coquinou).
La meilleure démonstration aurait été de servir des pages simples.

Quant au Visual Basic... Marc avait à l'époque été recruté pour remplacer cette techno qui était utilisée sur certains de nos sites. Je pense que notre avis est le même.
Visual Basic est un langage pour du prototypage. C'est un dialecte d'étude sympathique.

Marc Chouteau a dit…

Concernant le serveur apache, j'imagine qu'il y a le meme type de liste quelque part (fichier .htaccess me semble-t-il), une hashtable pour servir le bon type mime en fonction de l'extension demandée. cette liste existe dans IIS, j'aurais effectivement pu aller chercher l'info dans la metabase mais j'ai préféré faire un peu de keyword dans mon blog ;) je suis demasqué

Da Scritch a dit…

Effectivement, tu as deux solution :

la méthode rapid & dirty dans .htaccess (mais aussi dans httpd.conf) qui consiste à associer une extension à un type-mime via la directive “AddDocumentType”. Utile pour dépanner, mais les inconvénients qui vont avec : une extension ne décrit pas forcément la réalité du fichier.

Et la “magic-file” qui se base sur le comportenment contenentiel d'un document, qui est la méthode recommandée. C'est le même dictionnaire utilisé pour la commande “file” option “-i”, dans la hiérarchie /usr/share/magic.* ou /etc/magic.*
C'est là où ça devient intéressant car on peut préciser le charset d'un text/* , les codecs et baudrates d'un conteneur audio/* ou video/*, voire même (chose fort utile pour vous dans l'univers Microsoft), la version du document excel.

Monsieur Chouteau, il faut vraiment que vous participiez à mes séminaires sur le placement de sites. Tu sais que je fais des miracles là-dedans.

Anonyme a dit…

Hello,nice post thanks for sharing?. I just joined and I am going to catch up by reading for a while. I hope I can join in soon.

Anonyme a dit…

I am looking forward to seeing more reports from it before. What you said was news to me, I′ll tell you.Thanks!