ChatGPT Image 28 mai 2026, 19_44_53.png

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,games

au lieu de :

/categories=books&categories=movies&categories=games

J’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 :

  1. intercepter la soumission du formulaire

  2. sérialiser manuellement les champs

  3. regrouper les checkbox par nom

  4. convertir les valeurs en CSV

  5. 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=movies

Mais avec une valeur CSV :

/categories=books,movies

MVC 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 :

  1. le ValueProvider récupère les valeurs de la query string

  2. le ModelBinder tente de convertir ces valeurs vers le type cible

  3. MVC 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,games

au lieu de :

/categories=books&categories=movies&categories=games

C’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[] categories

sans 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=B

Le 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.