Affichage des articles dont le libellé est Astuce. Afficher tous les articles
Affichage des articles dont le libellé est Astuce. Afficher tous les articles

Comment masquer un referer sur un lien sortant avec asp.net mv3

 

Pour diverses raisons il est indispensable de masquer le referer d’un lien sortant dans un site, par exemple lors d’un click dans un extranet ou certains paramètres de l’url peuvent apparaitre et devenir un point d’entrée pour une tentative d’intrusion.

Pour eviter de montrer des informations aux sites distants voici un exemple ce que 'qu’il est possible de faire :

on peut voir ligne 11 ce qu’il faut obtenir , une nouvelle methode d’extension de UrlHelper dans la responsabilité sera d’encoder le lien http://www.google.fr
voici le détail de la methode :
L’idée est de récuperer l’url “http://www.google.fr” et d’encoder la chaine de caractère en base 64 pour qu’elle passe correctement en paramètre meme si elle a elle meme des paramètres, on peut voir que l’url de redirection sera redirect?url=XXX
dans global.asax on va indiquer au gestionnaire de routes comment il doit traiter cette url :

Ligne 5 on indique l’action que doit effectuer le controller home dont voici le code :

on recupere le parametre url encodé en base64, on le décode et on retourne du code html généré dont le but sera d’utiliser la balise meta refresh pour justement masquer la precedente url, voici le resultat :

image

un click sur le lien google.fr, produit ceci :

image

Certe il y a un referer mais il est encodé, le site distant ne peut dont pas voir la page ou etait présent le lien.

Code source de la solution :

Sql longueur d’une chaine et espaces

On vient de me soumettre cette remarque :

select LEN(' ')

attention retourne 0

En effet il est écrit dans la doc (http://msdn.microsoft.com/fr-fr/library/ms190329.aspx)

“Retourne le nombre de caractères de l'expression de type chaine spécifiée, à l'exception des espaces de droite.”, ne respecte pas vraiment un des principe SOLID le SRP

pour avoir la vraie longueur utiliser plutôt

select DATALENGTH(' ')

retourne 1

IIS Multi host header et service WCF

Si plusieurs host headers sont déclarés dans une application web au niveau IIS et si l’on veut accéder à un service wcf voici l’erreur que l’on reçoit :

This collection already contains an address with scheme http.  There can be at most one address per scheme in this collection.
Parameter name: item

Une erreur pas très explicite, j’ai passé une bonne heure la dessus, pour faire fonctionner le service on peut soit declarer le host “principal” dans la section service dans web.config soit utiliser cette autre méthode que je préfere :

Au niveau du fichier svc, faire la déclaration suivante :

<%@ ServiceHost Service="MyService" Factory="MyServiceHostFactory" %>

puis créer une classe MyServiceHostFactory dont le but sera d’intercepter la requête et de filtrer les différents hosts pour n’en renvoyer qu’un seul.

public class MyServiceHostFactory : ServiceHostFactory



{



    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)



    {



        var addresses = new Uri[] { baseAddresses[0] };



        var serviceHost = new ServiceHost(serviceType, addresses);



        return serviceHost;



    }



}


Asp.net mvc 2 et RenderAction<> générique

J’ai déjà expliqué ici le peu d’interet que j’accorde aux “magic strings” qui sont souvent sources de bugs, depuis la nouvelle version d’asp.net mvc 2 pour faire du rendu partiel, il n’y à plus besoin d’utiliser les excellentes méthodes de MVCContrib car une nouvelle méthode permet le rendu

Html.RenderAction(ActionName, ControllerName, routeValues);

mais les paramètres ActionName et ControllerName sont de type string , dans le cas d’une refactorisation du nom d’une action ou d’un controlleur , il y a danger. pour éviter ça voici une méthode générique qui permet d’appeler les méthodes du controlleur choisi pour le rendu partiel :

1 public static void RenderAction<T>(this HtmlHelper helper, Expression<Action<T>> expression)
2 where T : Controller
3 {
4 var call = expression.Body as MethodCallExpression;
5 var controllerName = typeof(T).Name.Replace("Controller", string.Empty);
6 var actionName = call.Method.Name;
7
8 var parameters = call.Method.GetParameters();
9 var routeValues = new RouteValueDictionary();
10 if (parameters.Length > 0)
11 {
12 for (int i = 0; i < parameters.Length; i++)
13 {
14 var parameter = call.Arguments[i];
15 object value = null;
16 var constantExpression = parameter as ConstantExpression;
17 if (constantExpression != null)
18 {
19 value = constantExpression.Value;
20 }
21 else
22 {
23 var body = Expression.Convert(parameter, typeof(object));
24 var exp = Expression.Lambda<Func<object>>(body, new ParameterExpression[0]);
25 value = exp.Compile()();
26 }
27 routeValues.Add(parameters[i].Name, value);
28 }
29 }
30 helper.RenderAction(actionName, controllerName, routeValues);
31 }


exemple :


Html.RenderAction<AccountController>(c => c.PartialLoginBox(“login.ascx”));



Bien sur ceci est au détriment de la performance, mais on gagne en robustesse de code.

Utiliser Atos SIPS sur windows 2008 R2 64 bits

J’ai eu à mettre en place le systeme de paiement par carte bleue basé sur Atos SIPS de la société générale pour ERPStore sur un serveur Microsoft Windows 2008 R2 en 64 bits.

Autant dire tout suite que le setup téléchargeable sur le site de la banque est ecrit en 16bits et donc ne peut pas se lancer sur une machine recente en 64 bits, j’ai donc téléchargé uniquement les binaires et installé ceux-ci dans un répertoire dedié comme préconisé par la documentation.

La documentation préconise d’installer le composant COM “SIPSExec.dll” via regsvr32.exe , comme indiqué plus haut, le setup ne fonctionnant pas sur une machine 64bits.

L’installation se deroule sans pb j’ai un message d’installation positif, mais lors du test de la sandbox j’ai un message plutot succinct ou je deduis via quelques requetes google que j’ai un pb de securité ou autre. (Au passage la meme procedure sur mon serveur de préprod en 32bits ne m’a posé aucun pb).

En regardant bien ce composant COM, j’en deduit qu’il s’agit finalement d’un wrapper qui permet de lancer les 2 executables founis avec l’API SIPS request.exe et response.exe.

Voici comment j’ai procedé pour me passer du composant COM et lancer directement les executables.

		public string ExecuteSips(string path, string parameters)
{
var psi = new System.Diagnostics.ProcessStartInfo(path, parameters);
psi.RedirectStandardOutput = true;
psi.ErrorDialog = false;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
string result = null;
try
{
var p = System.Diagnostics.Process.Start(psi);
var reader = p.StandardOutput;
result = reader.ReadToEnd();
reader.Close();
}
catch (Exception ex)
{
// TODO : Log de l’erreur


				throw;
}
return result;
}


Je passe en parametre “path” le chemin vers request.exe ou response.exe et les differents paramètres “parameters” comme indiqué dans la documentation, cette methode me retourne alors soit le code HTML du formulaire avec les paramètres encryptés pour se diriger vers la banque. Soit me retourne le code décrypté lors de l’appel du serveur par la banque.


Il n’y a finalement pas besoin d’installer de composant , simplement si le site web tourne avec le compte “NetworkService”, il faut donner les droits d’execution à ce compte sur les fichiers Request.exe et Response.exe.

Comment tester les routes Asp.Net MVC

lors d’une phase de test, j’ai été amené a devoir tester un certain nombre de routes pour savoir quels Controllers et Actions etaient matchés, voici comment j’ai procédé.

Dans un premier temps il faut creer une classe HttpContext “bouchon” qui herite de HttpContextBase

public class MockHttpContext : HttpContextBase
{
private readonly string _relativeUrl;
private readonly Models.UserPrincipal _principal;
private readonly NameValueCollection _formParams;
private readonly NameValueCollection _queryStringParams;
private readonly HttpCookieCollection _cookies;
private readonly SessionStateItemCollection _sessionItems;


public MockHttpContext(string relativeUrl)
: this(relativeUrl, null, null, null, null, null)
{
}

public MockHttpContext(string relativeUrl, Models.UserPrincipal principal, NameValueCollection formParams, NameValueCollection queryStringParams, HttpCookieCollection cookies, SessionStateItemCollection sessionItems)
{
_relativeUrl = relativeUrl;
_principal = principal;
_formParams = formParams;
_queryStringParams = queryStringParams;
_cookies = cookies;
_sessionItems = sessionItems;
}

public override HttpRequestBase Request
{
get
{
return new MockHttpRequest(_relativeUrl, _formParams, _queryStringParams, _cookies);
}
}

public override IPrincipal User
{
get
{
return _principal;
}
set
{
throw new System.NotImplementedException();
}
}

public override HttpSessionStateBase Session
{
get
{
return null;
}
}

}
Et une classe HttpRequest “Bouchon”
	public class MockHttpRequest : HttpRequestBase
{
private readonly string _relativeUrl;
private readonly NameValueCollection _formParams;
private readonly NameValueCollection _queryStringParams;
private readonly HttpCookieCollection _cookies;

public MockHttpRequest(string relativeUrl, NameValueCollection formParams, NameValueCollection queryStringParams, HttpCookieCollection cookies)
{
_relativeUrl = relativeUrl;
_formParams = formParams;
_queryStringParams = queryStringParams;
_cookies = cookies;
}

public override NameValueCollection Form
{
get
{
return _formParams;
}
}

public override NameValueCollection QueryString
{
get
{
return _queryStringParams;
}
}

public override HttpCookieCollection Cookies
{
get
{
return _cookies;
}
}

public override string AppRelativeCurrentExecutionFilePath
{
get { return _relativeUrl; }
}

public override string PathInfo
{
get { return String.Empty; }
}


}
Et pour pouvoir tester n’importe quelle url, il faut commencer par la création du context :

var fakeContext = new Serialcoder.ERPStore.Web.MockHttpContext(“/test”);

puis passer le context au gestionnaire des routes  pour obtenir les valeurs sur la route determinée :

var currentRoute = RouteTable.Routes.GetRouteData(context);

on verifie le “controller” et l’”action” retournés :

var controller =(string) ((KeyValuePair<string, object>)currentRoute.Values["controller"]).Value;

var action = (string) ((KeyValuePair<string, object>)currentRoute.Values["action"]).Value;

il ne reste plus qu’a faire les comparaisons de test
 

Compression GZip d’une vue avec asp.net mvc

Il est possible de compresser très facilement le contenu d’une vue avec asp.net mvc avant son rendu grace à un FilterAttribute, voici comment proceder :

	public class CompressFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;

string acceptEncoding = request.Headers["Accept-Encoding"];

if (string.IsNullOrEmpty(acceptEncoding))
{
return;
}

acceptEncoding = acceptEncoding.ToUpperInvariant();

var response = filterContext.HttpContext.Response;

if (acceptEncoding.IndexOf("GZIP", StringComparison.InvariantCultureIgnoreCase) != -1)
{
response.AppendHeader("Content-encoding", "gzip");
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
}
else if (acceptEncoding.IndexOf("DEFLATE", StringComparison.InvariantCultureIgnoreCase) != -1)
{
response.AppendHeader("Content-encoding", "deflate");
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
}
}
}


puis de placer cet attribut sur la vue à compresser :



		[CompressFilter]
public ActionResult Index()
{
return View();
}


Attention, si le serveur IIS est déjà configuré pour compresser les pages, cela peut creer des problèmes d’affichage.

Asp.net MVC et CatchAll

Je viens d’avoir une attaque sur un site asp.net, du classique /admin/pma/main.php, ect… certains doivent bien connaitre. au passage il ne faut jamais mettre son backoffice sur le meme host que le site front, voir si c’est possible et je prêche pour ma parroisse, ne pas avoir de backoffice du tout mais plutot un vrai logiciel de gestion qui permet de piloter le site front , mais ceci est un autre sujet.

Ces pages provoquaient des erreurs de type 500 qui poluaient le journal d’evenement.

J’ai ajouté un systeme de filtre qui me permet de voir les pages qui ne sont pas catchées par mes routes que je ne détaillerai pas ici et du coup j’ai pu éliminer les attaques *.php





Voici un moyen d’intercepter toutes les routes avec asp.net, dans la déclaration des routes (global.asax) il suffit d’ajouter celle-ci EN DERNIER attention !

RouteTable.Routes.MapRoute(
    "CatchAll"
    , "{*catchall}"
    , new { controller = "Home", action = "CatchAll" }
);

puis d’ajouter la methode CatchAll au controller Home

public ActionResult CatchAll()
{

Response.StatusCode = 404;

return View("404");

}

Lancer IE6, IE7 et IE8 sur la meme machine (mise a jour)

J’avais ecrit il y a quelque temps un article avec une solution un peu contraignante pour faire tourner  differentes versions d’internet explorer sur la meme machine, indispensable pour les web designers.

http://marc-chouteau.blogspot.com/2007/02/comment-executer-differentes-version-d.html

Microsoft a depuis,  mis en ligne gratuitement Expression Web SuperPreview for Windows Internet Explorer

http://www.microsoft.com/downloads/details.aspx?FamilyID=8e6ac106-525d-45d0-84db-dccff3fae677&displaylang=en

image

On voit bien sur le site yahoo.fr, 2 pages differentes en haut IE6 et en bas IE7/IE8, vraiment génial cet outil.

Methode d’extension pour reperer les sequences dans une liste

J’utilise cette methode d’extension pour reperer les lignes dans une liste et alterner une couleur de fond via une feuille de style.

public static string ColumnIndexName<T>(this IList<T> source, T item, int columnCount, string prefix)
{
var index = source.IndexOf(item);
return string.Format("{0}{1}", prefix, (index % columnCount) + 1);
}





Exemple d’utilisation avec asp.net mvc si Model implémente IList :




<div class="<%=Model.ColumnIndexName(item, 3, "prefix") %>">...</div>



ce qui donnera :



<div class=”prefix1”>…</div>



<div class=”prefix2”>…</div>



<div class=”prefix3”>…</div>



<div class=”prefix1”>…</div>



<div class=”prefix2”>…</div>



<div class=”prefix3”>…</div>



ect…

Récupération de la sortie html d’une vue asp.net mvc

Pour pouvoir templater des emails pour un site web en utilisant asp.net mvc, j’avais besoin de pouvoir recuperer la sortie html d’une vue sans avoir à recrawler une page (ce qui provoque des problèmes de statistiques et autres), en cherchant je suis tombé sur cet article très interessant :

http://jwbs-blog.blogspot.com/2009/08/chaining-aspnet-mvc-actions.html

Je l’ai un peu modifié pour en faire une methode d’extension :

public static string GetActionOutput<T>(this System.Web.Mvc.Controller ctrl, Expression<Action<T>> action)
{
var body = action.Body as MethodCallExpression;

var responseFilter = ctrl.HttpContext.Response.Filter;
var memoryStreamFilter = new MemoryStreamFilter(ctrl.HttpContext.Response.Filter);
ctrl.HttpContext.Response.Filter = memoryStreamFilter;

var routeData = ctrl.ControllerContext.RequestContext.RouteData;
string controllerName = body.Object.Type.Name.Replace("Controller", "");
string actionName = body.Method.Name;
var orgAction = routeData.Values["action"];
var orgController = routeData.Values["controller"];
routeData.Values["action"] = actionName;
routeData.Values["controller"] = controllerName;

var parameters = body.Method.GetParameters();
if (parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
var expression = body.Arguments[i];
object value = null;
var constantExpression = expression as ConstantExpression;
if (constantExpression != null)
{
value = constantExpression.Value;
}
else
{
var lambda = Expression.Lambda<Func<object>>(Expression.Convert(expression, typeof(object)), new ParameterExpression[0]);
value = lambda.Compile()();
}

routeData.Values.Add(parameters[i].Name, value);
}
}

var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
var c = controllerFactory.CreateController(ctrl.ControllerContext.RequestContext, controllerName);
c.Execute(ctrl.ControllerContext.RequestContext);

ctrl.HttpContext.Response.Flush();
memoryStreamFilter.Position = 0;
string result = null;
using (var r = new StreamReader(memoryStreamFilter))
{
result = r.ReadToEnd();
r.Close();
}

memoryStreamFilter.Close();
memoryStreamFilter.Dispose();

ctrl.HttpContext.Response.Filter = responseFilter;
routeData.Values["action"] = orgAction;
routeData.Values["controller"] = orgController;

return result;
}





voici la classe de wrapping pour recuperer la sortie en mémoire :




public class MemoryStreamFilter : MemoryStream
{
public MemoryStreamFilter(Stream stream)
{

}
}





pour utiliser cette methode d’extension, il suffit de faire un appel comme ceci :




public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";

var content = this.GetActionOutput<HomeController>(i => i.About());

return View();
}





il est possible de passer des paramètres, la methode est prevue pour ça.

ValidationSummary designable avec asp.net mvc

La methode d’extension Html.ValidationSummary() est certe pratique mais très vite limitée en terme de webdesign pour un rendu professionnel d’un message d’erreur de validation, pour etre plus precis, voici une solution que j’utilise :

Il faut creer un controle validationsummary.ascx (par exemple) à placer dans le repertoire courrant ou dans /shared

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<% if (!ViewData.ModelState.IsValid) { %>
<div class="validation">
<strong><img src="/content/images/icon_error.png" alt=""/>Attention !</strong>
<% foreach (var item in ViewData.ModelState.GetAllErrors()) { %>
<p class="error">
<span><%=item%></span>
</p>
<% } %>
</div>
<% } %>



Ajouter la methode d’extension GetAllErrors dans une classe helper :




/// <summary>
/// Retourne la liste de toutes les erreurs de validation d'un formulaire
/// </summary>
/// <param name="modelState">State of the model.</param>
/// <returns></returns>
public static IEnumerable<string> GetAllErrors(this ModelStateDictionary modelState)
{
var errors = from state in modelState
from error in state.Value.Errors
where state.Value.Errors.Count > 0
select error.ErrorMessage;

return errors;
}





puis dans la page ou se trouve le formulaire il suffit d’appeler un rendu partiel




<%Html.RenderPartial("ValidationSummary"); %>

Favicon.ico, asp.net mvc et 404

Sur un de mes sites j’ai constaté dans les logs IIS qu’un 404 se produisait car les navigateurs veulent obtenir le fichier favicon.ico, voici une solution pour eviter de renvoyer un 404 :

dans global.asax, ajouter une route :

routes.MapRoute(
"FavIcon"
, @"favicon.ico"
, new { controller = "Home", action = "FavIcon" }
);



dans le controller “HomeController” ajouter l’action FavIcon :




public ActionResult FavIcon()
{
var pixel = new byte[35] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61
, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF
, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00
, 0x02, 0x01, 0x44, 0x01, 0x00
, 0x3B };

return this.File(pixel, "image/gif");
}



le serveur retournera bien une reponse 200 au navigateur avec une image d’un pixel.



A savoir, si le fichier existe, pour eviter de passer par le controller par defaut, il faut ajouter la ligne suivante dans global.asax :




routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" });



Une variation pour l’action FavIcon() suite à la juste remarque de Dascritch :




[OutputCache(VaryByParam="none", Duration=3600)]
public ActionResult FavIcon()
{
byte[] result = null;
using (var bitmap = new System.Drawing.Bitmap(16, 16, System.Drawing.Imaging.PixelFormat.Format24bppRgb))
{
var backColor = bitmap.GetPixel(0, 0);
bitmap.MakeTransparent(backColor);
using (var ms = new System.IO.MemoryStream())
{
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
result = ms.GetBuffer();
ms.Close();
}
}
return this.File(result, "image/x-icon");
}




Retourne une image au format 16x16 transparent au format PNG, IE ne la prends pas en compte, Firefox est ok et Chrome affiche un carré noir :(

Binding et “refactorisation”

Un problème qui vient de m’arriver et qui est assez difficile à anticiper via des tests unitaires est le fait que d’utiliser le binding des controles dans une winform.

Par exemple si j’ai une textbox sur laquelle je veux binder le code d’un produit je vais ecrire :

myTextBox.DataBindings.Add("Text", bindingSource, "ProductCode");



Si maintenant je “refactore” ma classe product et change la propriété [ProductCode] par [Code] pour etre plus simple, tout mon projet sera changé, ça va compiler, sauf la ligne précédente il restera toujours “ProductCode”, etant donné que ce code est directement lié à l’interface graphique il est très difficile de reperer le bug via des tests unitaires avant d’ouvrir le formulaire manuellement, c’est du vecu, c’est plutot vos clients qui vous en parlent en premier :(



pour eviter ce problème, voici une solution :




public static class BindingExtensions
{
public static Binding Add<TSource>(this ControlBindingsCollection controlBindingCollection, string propertyName, object bindingSource, Expression<Func<TSource, object>> expression)
{
return controlBindingCollection.Add(propertyName, bindingSource, expression, false, controlBindingCollection.DefaultDataSourceUpdateMode, null, string.Empty, null);
}

public static Binding Add<TSource>(this ControlBindingsCollection controlBindingCollection, string propertyName, object bindingSource, Expression<Func<TSource, object>> expression, bool formattingEnabled)
{
return controlBindingCollection.Add(propertyName, bindingSource, expression, formattingEnabled, controlBindingCollection.DefaultDataSourceUpdateMode, null, string.Empty, null);
}

public static Binding Add<TSource>(this ControlBindingsCollection controlBindingCollection, string propertyName, object bindingSource, Expression<Func<TSource, object>> expression, bool formattingEnabled, DataSourceUpdateMode updateMode)
{
return controlBindingCollection.Add(propertyName, bindingSource, expression, formattingEnabled, updateMode, null, string.Empty, null);
}

public static Binding Add<TSource>(this ControlBindingsCollection controlBindingCollection, string propertyName, object bindingSource, Expression<Func<TSource, object>> expression, bool formattingEnabled, DataSourceUpdateMode updateMode, object nullValue)
{
return controlBindingCollection.Add(propertyName, bindingSource, expression, formattingEnabled, updateMode, nullValue, string.Empty, null);
}

public static Binding Add<TSource>(this ControlBindingsCollection controlBindingCollection, string propertyName, object bindingSource, Expression<Func<TSource, object>> expression, bool formattingEnabled, DataSourceUpdateMode updateMode, object nullValue, string formatString)
{
return controlBindingCollection.Add(propertyName, bindingSource, expression, formattingEnabled, updateMode, nullValue, formatString, null);
}

public static Binding Add<TSource>(this ControlBindingsCollection controlBindingCollection, string propertyName, object bindingSource, Expression<Func<TSource, object>> expression, bool formattingEnabled, DataSourceUpdateMode updateMode, object nullValue, string formatString, IFormatProvider formatInfo)
{
if (bindingSource == null)
{
throw new ArgumentNullException("dataSource");
}
var dataMemberName = ((expression.Body as UnaryExpression).Operand as MemberExpression).Member.Name;
return controlBindingCollection.Add(propertyName, bindingSource, dataMemberName, formattingEnabled, updateMode, nullValue, formatString, formatInfo);
}
}





J’ai ajouté dans mon projet une classe avec un certain nombre de methodes qui etendent l’objet ControlBindingCollection, la difference est que je ne passe plus le nom de la propriété mais utilise une lambda expression de la manière suivante :




myTextbox.DataBindings.Add<Product>("Text", bindingSource, p => p.ProductCode);



En cas de “refacto”, le nouveau nom de propriété sera changé.



Petit bémol avec cette méthode, il n’est plus question d’utiliser le designer intégré avec visual studio 2008, mais il faut choisir entre la maintenabilité et le confort pour le developpeur. Personnelement je n’utilise plus le designer pour tout ce qui concerne le binding.

Methode d’extension asp.net mvc pour ajouter un paramètre dans l’url

 

Voici une petite methode d’extension bien pratique pour pouvoir ajouter un parametre dans l’url courante

/// <summary>
/// Ajoute un parametre et sa valeur dans une URL en tenant compte de sa presence eventuelle
/// </summary>
/// <example>
/// Exemple d'utilisation
/// <code>
/// <![CDATA[
///
/// une url existante est de la forme
/// http://domain.com/test?param1=valeur1
///
/// <a href='<%=Url.AddParameter("param2", "valeur2")%>'>Mon lien</a>
///
/// Donnera le resultat suivant :
///
/// http://domain.com/test?param1=valeur1&param2=valeur2
///
/// <a href='<%=Url.AddParameter("param1", "valeur2")%>'>Mon lien</a>
///
/// Donnera le resultat suivant :
///
/// http://domain.com/test?param1=valeur2
///
/// ]]>
/// </code>
/// </example>
/// <remarks>
/// "value" est urlencodé par la methode, ne pas passer la valeur deja encodée
/// </remarks>
/// <param name="helper">The helper.</param>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns>Le lien avec le couple key=value</returns>
public static string AddParameter(this UrlHelper helper, string key, string value)
{
var link = helper.RequestContext.HttpContext.Request.Url.PathAndQuery;
var pattern = string.Format("&*{0}=[^&]*", key);
link = System.Text.RegularExpressions.Regex.Replace(link, pattern, "");
link = link.Trim('?').Trim('&');
var separator = (link.IndexOf("?") == -1) ? "?" : "&";
link += string.Format("{0}{1}={2}", separator, key, helper.Encode(value));
return link;
}

Comment obtenir le host d’un service WCF hébergé avec IIS

 

dans le cas d’un service wcf hébergé, System.Web.HttpContext.Current retourne null, il n’est donc pas possible de passer par lui pour obtenir la valeur du host, voici ci-dessous une methode pour y parvenir.

var serviceHost = System.ServiceModel.OperationContext.Current.InstanceContext.Host;
var baseAddress = serviceHost.BaseAddresses.First();
var host = baseAddress.AbsoluteUri.Replace(baseAddress.LocalPath, "");

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