13 Sep 2011 in
ASP.NET MVC
ASP.NET 4.0 apporte de nombreuses nouveautés et notamment la possibilité de développer son propre fournisseur de cache de sortie.
Pour des besoins bien particulier, un de mes clients avait besoin de générer un grand nombre de page dynamique : le problème étant que ces pages faisaient appel à énormément de ressources et donc la génération prenait un temps fou.
Pour résoudre le problème, j’ai proposé de faire un fournisseur de cache de sortie personnalisé permettant de stocker la réponse HTTP sur le FileSystem afin de pouvoir la renvoyer plus rapidement. Cette solution est assez intéressante car le cache reste disponible même si le pool d’application ASP.NET est coupé. En revanche, elle n’est pas optimale dans un cas multi-serveur avec un répartiteur de charge en amont. Bref, à ne pas utiliser les yeux fermés
Création du fournisseur de cache de sortie personnalisé
Pour créer un fournisseur de cache de sortie personnalisé, il suffit de dériver de la classe abstraite OutputCacheProvider et de redéfinir les méthodes suivantes :
public class FileSystemOutputCacheProvider : OutputCacheProvider
{
public override object Add(string key, object entry, DateTime utcExpiry)
{
throw new NotImplementedException();
}
public override object Get(string key)
{
throw new NotImplementedException();
}
public override void Remove(string key)
{
throw new NotImplementedException();
}
public override void Set(string key, object entry, DateTime utcExpiry)
{
throw new NotImplementedException();
}
}
Voilà un bref descriptif des méthodes :
-
Add : permet d’ajouter un élément identifié par une clé unique dans le cache
-
Get : permet de récupérer un élément dans le cache
-
Remove : permet de supprimer un élément du cache
-
Set : permet de mettre à jour un élément dans le cache
Les éléments mis en cache seront de type CachedItem
:
[Serializable]
public class CachedItem
{
public object Item { get; set; }
public DateTime UtcExpiry { get; set; }
}
Nous utiliserons également deux méthodes permettant de générer un chemin de fichier à partir de la clé passé par le moteur ASP.NET MVC et de sauvegarder un CachedItem sur le disque :
private void SaveCachedItem(CachedItem cachedItem, string filePath)
{
if (File.Exists(filePath))
File.Delete(filePath);
using (var stream = File.OpenWrite(filePath))
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(stream, cachedItem);
}
}
private string GetFilePathFromKey(string key)
{
foreach (var invalidChar in Path.GetInvalidFileNameChars())
key = key.Replace(invalidChar, '_');
return Path.Combine(CacheDirectory, key);
}
Méthode Add :
public override object Add(string key, object entry, DateTime utcExpiry)
{
string filePath = GetFilePathFromKey(key);
var cachedItem = GetCachedItem(filePath);
if (cachedItem != null && cachedItem.UtcExpiry.ToUniversalTime() <= DateTime.UtcNow)
{
Remove(key);
}
else if (cachedItem != null)
{
return cachedItem.Item;
}
SaveCachedItem(new CachedItem()
{
Item = entry,
UtcExpiry = utcExpiry
}, filePath);
return entry;
}
Méthode Get :
public override object Get(string key)
{
string filePath = GetFilePathFromKey(key);
var cachedItem = GetCachedItem(filePath);
if (cachedItem != null)
{
if (cachedItem.UtcExpiry.ToUniversalTime() <= DateTime.UtcNow)
{
Remove(key);
}
else
{
return cachedItem.Item;
}
}
return null;
}
Méthode Remove :
public override void Remove(string key)
{
string filePath = GetFilePathFromKey(key);
if (File.Exists(filePath))
File.Delete(filePath);
}
Méthode Set :
public override void Set(string key, object entry, DateTime utcExpiry) {
string filePath = GetFilePathFromKey(key);
var cachedItem = new CachedItem() {Item = entry, UtcExpiry = utcExpiry};
SaveCachedItem(cachedItem, filePath);
}
Enregistrement du fournisseur de cache de sortie personnalisé
L’enregistrement d’un fournisseur de cache de sortie se fait en deux étapes : dans le Web.config et dans le Global.asax.
Fichier Web.config :
<system.web>
<caching>
<outputCache>
<providers>
<add name="FileSystemOutputCacheProvider"
type="Samples.FileSystemOutputCacheProvider, Samples"/>
</providers>
</outputCache>
</caching>
Global.asax :
Il faut surcharger la méthode **GetOutputCacheProviderName** qui est en charge de retourner le fournisseur contextuel à la requête. Par exemple, si l’on souhaite utiliser le FileSystemCacheProvider que sur le contrôleur `Products`, on écrira :
public override string GetOutputCacheProviderName(HttpContext context)
{
if (context.Request.RawUrl.ToUpper().Contains("PRODUCTS/DETAILS")) {
return "FileSystemOutputCacheProvider";
}
return base.GetOutputCacheProviderName(context);
}
Utilisation du fournisseur de cache de sortie personnalisé en ASP.NET MVC
Pour utiliser le nouveau fournisseur de cache, il suffit d’utiliser l’attribut “OutputCache` comme à son habitude. C’est le Framework ASP.NET qui se chargera d’instancier le bon fournisseur de cache au bon moment
public class ProductsController : Controller
{
[OutputCache(Duration = 3600, VaryByParam = "id")]
public ActionResult Details(int id)
{
//long operation
Thread.Sleep(2000);
ViewBag.ProductId = id;
return View();
}
}
NB : le fait de préciser ici le paramètre VaryByParam = id
permet de faire en sorte que le moteur MVC génère une clé différente par id, est donc implicitement que le FileSystemOutputCacheProvider génère un fichier par id !
Sources de l’exemple
A bientôt
read more
06 Sep 2011 in
General
I’m currently working in a company where I have to set up a proxy to connect my laptop to the Internet. Because I’m fed up to enable it each morning and disable it each evening, I made two Powershell scripts to do these operations :
EnableProxy.ps1 :
set-itemproperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
-name ProxyEnable -value 1
DisableProxy.ps1 :
set-itemproperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
-name ProxyEnable -value 0
Et voilà !
Hope this helps
read more
06 Sep 2011 in
General
En ce moment je suis chez un client où je dois activer un proxy pour me connecter à Internet. Comme j’en avais assez de devoir reconfigurer mon IE tous les soirs chez moi et tous les matins chez mon client, voilà deux scripts Powershell permettant de faire celà en deux clics :
EnableProxy.ps1 :
set-itemproperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
-name ProxyEnable -value 1
DisableProxy.ps1
set-itemproperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
-name ProxyEnable -value 0
Simple et efficace !
A bientôt
read more
05 Sep 2011 in
ASP.NET MVC
As you probably know ASP.NET MVC supports .NET Framework 4 Data Annotations to validate user’s inputs (Required, StringLength, Range, RegularExpression…)
Another attribute exists and allows asynchronous client side validation. It’s the Remote attribute. For example, it can be used to validate e-mail and username in a registration form and alert the user before the form is posted.
Here is a sample model for a registration form :
public class UserModel
{
[Required]
[Remote("CheckUsername", "RemoteValidation", ErrorMessage = "This username is already used.")]
public string Username { get; set; }
[Required]
[Remote("CheckEmail", "RemoteValidation", ErrorMessage = "This e-mail is already used.")]
public string Email { get; set; }
[StringLength(80)]
public string FirstName { get; set; }
[StringLength(80)]
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
As you can see in the previous code snippet, the Remote attribute takes two parameters :
The controller :
public class RemoteValidationController : Controller
{
private readonly string[] _existingUsernames = {"beedoo", "julien", "jcorioland"};
private readonly string[] _existingEmails = { "[email protected]" };
public JsonResult CheckUsername(string username) {
bool userIsAvailable = !_existingUsers.Contains(username);
return Json(userIsAvailable, JsonRequestBehavior.AllowGet);
}
public JsonResult CheckEmail(string email)
{
bool emailIsAvailable = !_existingEmails.Contains(email);
return Json(emailIsAvailable, JsonRequestBehavior.AllowGet);
}
}
These actions are very simple : they take the input to validate as a string parameter and return a Json-serialized Boolean that indicates if the input is valid or not. For this article I’ve used two arrays to represent users and e-mails databases but it’s possible to execute any code here.
The view :
@using (Html.BeginForm())
{
<div class="editor-label">@Html.LabelFor(m => m.Username)
<div class="editor-field">
@Html.TextBoxFor(m => m.Username)
@Html.ValidationMessageFor(m => m.Username)
<div class="editor-label">@Html.LabelFor(m => m.Email)
<div class="editor-field">
@Html.TextBoxFor(m => m.Email)
@Html.ValidationMessageFor(m => m.Email)
<div class="editor-label">@Html.LabelFor(m => m.FirstName)
<div class="editor-field">
@Html.TextBoxFor(m => m.FirstName)
@Html.ValidationMessageFor(m => m.FirstName)
<div class="editor-label">@Html.LabelFor(m => m.LastName)
<div class="editor-field">
@Html.TextBoxFor(m => m.LastName)
@Html.ValidationMessageFor(m => m.LastName)
<div class="editor-label">@Html.LabelFor(m => m.BirthDate)
<div class="editor-field">
@Html.TextBoxFor(m => m.BirthDate)
@Html.ValidationMessageFor(m => m.BirthDate)
<input type="submit" value="S'enregistrer" />
}
To activate the client side validation the following jQuery plugins should be loaded :
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
</head>
The validation actions are automatically called (asynchronously of course) when the user is completing the register form :
Hope this helps
read more
05 Sep 2011 in
ASP.NET MVC
Comme vous le savez peut-être déjà, ASP.NET MVC 3 supporte les DataAnnotations du .NET Framework 4 pour tout ce qui est validation des entrées utilisateurs : Required, StringLength, Range, RegularExpression…
Il existe un attribut un peu moins connu : Remote. Celui-ci permet de lancer une validation asynchrone côté client. Par exemple, sur un formulaire d’inscription il est fréquent de vouloir valider de manière asynchrone qu’un nom d’utilisateur / e-mail n’existe pas déjà en base utilisateur.
Voilà un exemple de modèle pour parvenir à cela :
public class UserModel
{
[Required]
[Remote("CheckUsername", "RemoteValidation", ErrorMessage = "Ce nom d'utilisateur est déjà utilisé")]
public string Username { get; set; }
[Required]
[Remote("CheckEmail", "RemoteValidation", ErrorMessage = "Cet e-mail est déjà utilisé")]
public string Email { get; set; }
[StringLength(80)]
public string FirstName { get; set; }
[StringLength(80)]
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
Comme il est possible de le constater dans l’extrait de code ci-dessus, l’attribut remote prend deux paramètres :
-
L’action MVC en charge de la validation
-
Le contrôlleur dans lequel l’action est défini (ici, RemoteValidationController)
Voilà le code de ce contrôlleur :
public class RemoteValidationController : Controller
{
private readonly string[] _existingUsernames = {"beedoo", "julien", "jcorioland"};
private readonly string[] _existingEmails = { "[email protected]" };
public JsonResult CheckUsername(string username) {
bool userIsAvailable = !_existingUsers.Contains(username);
return Json(userIsAvailable, JsonRequestBehavior.AllowGet);
}
public JsonResult CheckEmail(string email)
{
bool emailIsAvailable = !_existingEmails.Contains(email);
return Json(emailIsAvailable, JsonRequestBehavior.AllowGet);
}
}
Les actions sont ultra simples : elles prennent en paramètre le terme à valider et retourne un booléean, sérialisé en Json.
NB : ici la source d’emails/usernames existants a été simplifiée, mais un appel à une base de données est tout à fait possible.
La vue pour tester :
@using (Html.BeginForm())
{
<div class="editor-label">@Html.LabelFor(m => m.Username)
<div class="editor-field">
@Html.TextBoxFor(m => m.Username)
@Html.ValidationMessageFor(m => m.Username)
<div class="editor-label">@Html.LabelFor(m => m.Email)
<div class="editor-field">
@Html.TextBoxFor(m => m.Email)
@Html.ValidationMessageFor(m => m.Email)
<div class="editor-label">@Html.LabelFor(m => m.FirstName)
<div class="editor-field">
@Html.TextBoxFor(m => m.FirstName)
@Html.ValidationMessageFor(m => m.FirstName)
<div class="editor-label">@Html.LabelFor(m => m.LastName)
<div class="editor-field">
@Html.TextBoxFor(m => m.LastName)
@Html.ValidationMessageFor(m => m.LastName)
<div class="editor-label">@Html.LabelFor(m => m.BirthDate)
<div class="editor-field">
@Html.TextBoxFor(m => m.BirthDate)
@Html.ValidationMessageFor(m => m.BirthDate)
<input type="submit" value="S'enregistrer" />
}
Enfin, assurez vous de charger les plugins jQuery UI et jQuery validate pour que cela fonctionne :
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
</head>
Dès lors, les actions de validation sont appelées en asynchrone lors de la saisie du formulaire, permettant ainsi une aide à la saisie avancée pour l’utilisateur !
NB : et comme toujours, la validation côté client ne doit absolument pas remplacer la validation côté serveur !
A bientôt
read more