Des paramètres CSV en Querystring
Comment uitiliser des paramètres CSV pour gérer des checkbox multiples en ASP.NET MVC
Lorsqu’on développe des systèmes de filtres dans une application web, un problème revient souvent : comment représenter proprement plusieurs cases cochées dans une query string. Par exemple :
/categories=books,movies,gamesau lieu de :
/categories=books&categories=movies&categories=gamesJ’ai récemment mis en place une approche légère basée sur :
une sérialisation JavaScript personnalisée
un binder MVC spécifique côté serveur
Le but est d’obtenir des URLs plus compactes tout en conservant un binding fortement typé dans ASP.NET MVC.
La partie JavaScript
L’idée est simple :
intercepter la soumission du formulaire
sérialiser manuellement les champs
regrouper les checkbox par nom
convertir les valeurs en CSV
reconstruire l’URL avec les paramètres
$(function () {
document.querySelectorAll('form').forEach(form => {
form.addEventListener('submit', function (e) {
e.preventDefault();
const params = new URLSearchParams();
// serialize text, hidden, select inputs
form.querySelectorAll(
'input[type="text"], input[type="hidden"], select'
).forEach(el => {
if (el.value) params.set(el.name, el.value);
});
// serialize all multi-checkboxes as CSV automatically
const checkboxGroups = {};
form.querySelectorAll('input[type="checkbox"]:checked')
.forEach(cb => {
if (!checkboxGroups[cb.name]) {
checkboxGroups[cb.name] = [];
}
checkboxGroups[cb.name].push(cb.value);
});
for (const [name, values] of Object.entries(checkboxGroups)) {
params.set(name, values.join(','));
}
// redirect or ajax
const url = `${form.action}?${params.toString()}`;
window.location.href = url;
});
});
});Le binder ASP.NET MVC
Par défaut, ASP.NET MVC sait binder automatiquement les tableaux avec des paramètres répétés :
/categories=books&categories=moviesMais avec une valeur CSV :
/categories=books,moviesMVC ne voit qu’une simple chaîne de caractères :
"books,movies"Il faut donc intervenir dans le pipeline de binding avec un binder personnalisé.
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class CsvArrayBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var value = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName)
.FirstValue;
if (string.IsNullOrWhiteSpace(value))
{
bindingContext.Result = ModelBindingResult.Success(
Array.CreateInstance(
bindingContext.ModelType.GetElementType() ?? typeof(string),
0));
return Task.CompletedTask;
}
var elementType =
bindingContext.ModelType.GetElementType() ?? typeof(string);
var items = value.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(v => ConvertValue(v, elementType))
.ToArray();
var typedArray = Array.CreateInstance(elementType, items.Length);
items.CopyTo(typedArray, 0);
bindingContext.Result = ModelBindingResult.Success(typedArray);
return Task.CompletedTask;
}
private object? ConvertValue(string value, Type targetType)
{
try
{
if (targetType == typeof(string))
return value;
return Convert.ChangeType(value, targetType);
}
catch
{
return null;
}
}
}Exemple d’utilisation :
public IActionResult Search(
[ModelBinder(BinderType = typeof(CsvArrayBinder))]
string[] categories)
{
...
}
Ce qui se passe “sous le capot”
Dans le pipeline MVC :
le
ValueProviderrécupère les valeurs de la query stringle
ModelBindertente de convertir ces valeurs vers le type cibleMVC sait naturellement gérer :
les types simples
les objets complexes
les tableaux avec paramètres répétés
Mais dans le cas d’un CSV, MVC ne découpe pas automatiquement les valeurs.
Le binder personnalisé vient donc étendre ce comportement :
récupération de la valeur brute
découpage avec
Split(',')conversion vers le type cible
création du tableau typé
injection dans le paramètre de l’action MVC
On ajoute ainsi une nouvelle convention de binding à l’infrastructure ASP.NET.
Les avantages
Des URLs plus lisibles
Les query strings deviennent plus compactes :
/categories=books,movies,gamesau lieu de :
/categories=books&categories=movies&categories=gamesC’est particulièrement agréable pour :
les systèmes de filtres
les moteurs de recherche internes
les dashboards
les interfaces d’administration
Une logique centralisée
Toute la logique de parsing est concentrée dans le binder.
Les contrôleurs restent propres :
string[] categoriessans parsing manuel.
Un frontend indépendant du framework
La solution JavaScript fonctionne avec :
HTML natif
Razor
jQuery
React
Vue
Angular
car elle repose simplement sur URLSearchParams.
Un backend fortement typé
Le binder permet de recevoir directement :
string[]
int[]
Guid[]
sans manipulation supplémentaire.
Les inconvénients
Le problème des virgules
Si une valeur contient elle-même une virgule :
science,fiction
le parsing devient ambigu.
Solutions possibles :
interdire les virgules
utiliser un autre séparateur
encoder chaque valeur
utiliser du JSON
Ce n’est pas le comportement standard de MVC
Un développeur habitué à ASP.NET peut s’attendre à :
/categories=A&categories=BLe CSV introduit une convention personnalisée qu’il faut documenter.
Support limité aux tableaux
L’implémentation actuelle cible principalement :
string[]Elle ne gère pas encore automatiquement :
List<T>
HashSet<T>
IEnumerable<T>
même si cela peut être ajouté facilement.
Améliorations possibles
Support des collections génériques
Étendre le binder à :
List<int>
List<Guid>
Support des types nullable
Gérer correctement :
int?
Guid?
Binder global
Créer un ModelBinderProvider pour enregistrer automatiquement le binder.
Support AJAX
Remplacer la redirection :
window.location.href = url;
par :
fetch(url)Conclusion
Cette approche fonctionne très bien pour les interfaces riches en filtres.
Elle permet :
des URLs plus compactes
un binding fortement typé
une logique centralisée
moins de parsing manuel dans les contrôleurs
ASP.NET MVC supporte déjà les tableaux via des paramètres répétés, mais le format CSV apporte une alternative élégante lorsqu’on souhaite contrôler précisément la structure des query strings.