[MVC] Utilisation de jQuery DataTables en mode serveur

Note: le projet est disponible sur GitHub : https://github.com/MarienMonnier/softit-jquerydatatables-demo

Dans cet article, nous allons voir comment le plus simplement possible, vous allez pouvoir utiliser jQuery DataTables (version 1.10 minimum) en mode serveur, pour récupérer vos données via ASP.Net MVC 5.

Il existe déjà plusieurs projets qui montrent comment utiliser ASP.Net MVC avec jQuery DataTables, mais au final, dans tous les exemples que j’ai trouvé, ils utilisaient tous un custom IModelBinder pour pouvoir faire le binding des différentes propriétés avec un View Model. Sauf qu’en utilisant MVC 5 et jQuery DataTables 1.10 (et supérieur), ce custom binding n’est pas nécessaire, et il enlève une bonne épine du pied.

La mise en place d’une DataTables en mode serveur avec ASP.Net MVC va se faire en plusieurs étapes :

  1. Création des View Models
  2. Création de la vue
  3. Création du Controller et des services

Et parce qu’il est toujours intéressant de pouvoir ajouter des filtres personnalisés, nous finirons par :

  1. Ajout de filtres supplémentaires

Création des View Models

DataTables envoie et récupère des données sous un certain format qui lui est spécifique. À partir des informations fournies par la documentation, nous récréons d’abord les View Models.

Le View Model pour le résultat global :

public class DTResult<T>
{
    public int draw { getset; }
    public int recordsTotal { getset; }
    public int recordsFiltered { getset; }
    public List<T> data { getset; }
}

Et le View Model des paramètres qui sont envoyés par DataTables à la requête AJAX. La seule propriété qui ne fait pas partie de la requête est « SortOrder », qui est une propriété maison permettant de faire un tri sur nos données via la méthode d’extension SortBy<T> de l’objet IQueryable<T>.

public class DTParameters
{
    public int Draw { getset; }
    public DTColumn[] Columns { getset; }
    public DTOrder[] Order { getset; }
    public int Start { getset; }
    public int Length { getset; }
    public DTSearch Search { getset; }
    public string SortOrder
    {
        get
        {
            return Columns != null && Order != null && Order.Length > 0
                ? (Columns[Order[0].Column].Data + (Order[0].Dir == DTOrderDir.DESC ? " " + Order[0].Dir : string.Empty))
                : null;
        }
    }
}

Création de la vue

Dans le cadre de cet exemple, nous allons afficher une liste de produits de la base Northwind récupérés avec les différents appels AJAX de DataTables.

Tout d’abord la partie HTML (« bootstrapée »):

<div class="panel panel-default">
    <div class="panel-heading">Product Search</div>
    <table class="table table-striped table-hover table-bordered table-responsive" id="dt" width="100%">
        <thead>
            <tr>
                <th data-column="ID" data-order="desc">ID</th>
                <th data-column="Name">Name</th>
                <th data-column="UnitPrice">UnitPrice</th>
            </tr>
        </thead>
    </table>
</div>

Ensuite, la partie JavaScript qui fait appel à DataTables. Il n’y a rien de particulier dans ce code, c’est la configuration de base de DataTables pour :

  • faire les appels en mode serveur (processing: true et serverSide: true)
  • lui indiquer que la page à appeler est notre méthode GetData de notre HomeController
  • configurer les colonnes à afficher.
<script type="text/javascript">
    $(function() {
        var table = $('#dt');
        var dt = table.dataTable({
            ajax: {
                type: 'POST',
                url: '@Url.Action("GetData""Home")'
            },
            columns: [
                { data: 'ID' },
                { data: 'Name' },
                { data: 'UnitPrice' }
            ],
            order: [0, 'desc'],
            processing: true,
            serverSide: true,
            orderMulti: false
        });
    });
</script>

Création du Controller et des services

La table est maintenant configurée pour appeler l’action GetData du HomeController.

Notre contrôleur a une méthode GetData qui :

  • prend en paramètre le View Model DTParameters que nous avons écrit et qui contient tous les paramètres que DataTables nous envoie
  • fait appel à un service (GetProducts) pour récupérer les produits en fonction de ces paramètres
  • fait appel à un second service (Count) pour récupérer le nombre d’éléments suivant ces paramètres (pour la pagination)
  • retourne les données au format JSON telles qu’attendues par DataTables (un DTResult<Product>).
public JsonResult GetData(DTParameters dtModel)
{
    try
    {
        List<Product> data = new ProductService().GetProducts(dtModel.Search.Value, dtModel.SortOrder, dtModel.Start, dtModel.Length);
        int count = new ProductService().Count(dtModel.Search.Value);
        DTResult<Product> result = new DTResult<Product>
                            {
                                draw = dtModel.Draw,
                                data = data,
                                recordsFiltered = count,
                                recordsTotal = count
                            };
        return Json(result);
    }
    catch (Exception ex)
    {
        return Json(new { error = ex.Message });
    }
}

Et les méthodes de service associées :

public class ProductService
{
    private static readonly List<Product> Products;
 
    static ProductService()
    {
        Products = new List<Product>
                    {
                        new Product(1, "Chai", 18.0000m),
                        new Product(2, "Chang", 19.0000m),
                        // ...
                        new Product(76, "Lakkalikööri", 18.0000m),
                        new Product(77, "Original Frankfurter grüne Soße", 13.0000m)
                    };
    }
 
    public List<Product> GetProducts(string search, string sortOrder, int start, int length)
    {
        return FilterProducts(search).SortBy(sortOrder).Skip(start).Take(length).ToList();
    }
 
    public int Count(string search)
    {
        return FilterProducts(search).Count();
    }
 
    private IQueryable<Product> FilterProducts(string search)
    {
        IQueryable<Product> results = Products.AsQueryable();
 
        if (!string.IsNullOrWhiteSpace(search))
            results = results.Where(p => p.Name.Contains(search));
 
        return results;
    }
}

Les services sont plutôt simples :

  • une méthode pour récupérer une liste de produits suivant une recherche, un tri, et une pagination
  • une méthode qui permet de compter le nombre d’éléments suivant le filtre donné (pour que la pagination soit effective côté jQuery DataTables).

Nous avons à présent une démo fonctionnelle qui nous permet de lister les produits de manière paginée, de changer le nombre d’éléments par page, et de rechercher un produit par son nom :

Ajout de filtres supplémentaires

Il peut toujours être intéressant de passer des données supplémentaires à vos appels AJAX qui sont faits par DataTables, ne serait-ce que pour ajouter des filtres, passer un ID supplémentaire, etc…

Toutes ces données sont à ajouter dans le paramètre data de DataTables.

On va modifier un peu la vue et le JavaScript pour ajouter un panel de filtre sur nos prix minimum et maximum; et un bouton pour lancer la recherche.

<div class="panel panel-default">
    <div>...</div>
    <div class="panel-body">
        <div class="well">
            <div class="row">
                <div class="col-xs-4">
                    <label for="minPrice">Minimum Price</label>
                    <input type="text" name="minPrice" id="minPrice" value="" class="form-control" />
                </div>
                <div class="col-xs-4">
                    <label for="maxPrice">Maximum Price</label>
                    <input type="text" name="maxPrice" id="maxPrice" value="" class="form-control" />
                </div>
                <div class="col-xs-2">
                    <button id="search" class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
                </div>
            </div>
        </div>
    </div>
    <table>...</table>
</div>

Nous passons les filtres dans la fonction data de DataTables, et rafraîchissons la table lorsque l’on clique sur le bouton de recherche :

var dt = table.dataTable({
    ajax: {
        type: 'POST',
        url: '@Url.Action("GetData""Home")',
        data: function(d) {
            d.MinPrice = $('#minPrice').val();
            d.MaxPrice = $('#maxPrice').val();
        }
    },
    columns: [ ... 
});
$('#search').click(function(e) {
    e.preventDefault();
    dt.fnDraw();
});

Nous avons maintenant une vue qui nous permet de sélectionner un prix minimum et maximum. Mettons à jour le Controller et les services pour prendre en compte ces nouvelles données.

Nous passons dans l’appel AJAX deux nouvelles propriétés (MinPrice et MaxPrice), que nous allons mettre dans une classe FilterViewModel très simple :

public class FilterViewModel
{
    public decimal? MinPrice { getset; }
    public decimal? MaxPrice { getset; }
}

Et nous faisons évoluer notre méthode GetData du Controller pour prendre aussi un FilterViewModel en paramètre, et passer les valeurs à nos deux méthodes de service :

public JsonResult GetData(DTParameters dtModelFilterViewModel filterModel)
{
    try
    {
        List<Product> data = new ProductService().GetProducts(dtModel.Search.Value, dtModel.SortOrder, dtModel.Start, dtModel.Length, filterModel.MinPrice, filterModel.MaxPrice);
        int count = new ProductService().Count(dtModel.Search.Value, filterModel.MinPrice, filterModel.MaxPrice);
        DTResult<Product> result = new DTResult<Product>
                            {
                                draw = dtModel.Draw,
                                data = data,
                                recordsFiltered = count,
                                recordsTotal = count
                            };
        return Json(result);
    }
    catch (Exception ex)
    {
        return Json(new { error = ex.Message });
    }
}

Faisons évoluer nos méthodes de service pour y ajouter un filtre sur le prix minimum et maximum :

public List<Product> GetProducts(string search, string sortOrder, int start, int lengthdecimal? minPrice, decimal? maxPrice)
{
    return FilterProducts(search, minPrice, maxPrice).SortBy(sortOrder).Skip(start).Take(length).ToList();
}
 
public int Count(string searchdecimal? minPrice, decimal? maxPrice)
{
    return FilterProducts(search, minPrice, maxPrice).Count();
}
 
private IQueryable<Product> FilterProducts(string searchdecimal? minPrice, decimal? maxPrice)
{
    IQueryable<Product> results = Products.AsQueryable();
 
    if (!string.IsNullOrWhiteSpace(search))
        results = results.Where(p => p.Name.Contains(search));
 
    if (minPrice.HasValue)
        results = results.Where(p => p.UnitPrice >= minPrice.Value);
 
    if (maxPrice.HasValue)
        results = results.Where(p => p.UnitPrice <= maxPrice.Value);
 
    return results;
}

Et voilà !

Nous avons maintenant une démo qui nous permet avec jQuery DataTables 1.10 de faire des requêtes serveur sur notre site MVC 5 en ajoutant des filtres personnalisés, tout cela en quelques minutes seulement.

Pour rappel, ce projet se trouve sous GitHub à l’adresse suivante : https://github.com/MarienMonnier/softit-jquerydatatables-demo

Et, comme je le signalais au début de l’article, il existe déjà plusieurs projets qui ont été faits pour MVC / jQuery DataTables; projets qui peuvent vous intéresser si vous utilisez des versions précédentes de MVC et/ou jQuery DataTables :

8 réflexions sur “[MVC] Utilisation de jQuery DataTables en mode serveur

  1. Budicom

    Bonjour,

    J'ai un projet professionnel en cours avec ASPnet MVC 5 EF6 et mysql (base déjà existante).
    N'étant pas développeut et pas encore très à l'aise, je bute sur le contrôleur avec l'utilisation de LINQ du genre :

    public ActionResult Index()
    {
    var inventaire = db.inventaire.Include(e => e.categorie).Include(e => e.etat).Include(e => e.pays);
    return View(inventaire .ToList());
    }
    pour le passer avec json. je ne vois pas trop comment adapter le code.

    Cdlt,

    Budicom

    J'aime

  2. John Horan

    Thank you for this beautiful and informative project.

    I have experience with ASP.NET Webforms but am new to MVC and jQuery DataTables. My question concerns how I need to modify your code if, instead of using a static list of Products, I need to retrieve the data from a Products table in a SQL Server database?.

    Say, for example, the Products table has 5 fields — int ID, string Name, decimal UnitPrice, string Manufacturer, and datetime Date — and I wish to retrieve only the ID, Name and UnitPrice fields. I have tried to figure out how to change the code to call data from the database but have not yet been successful. I'd be grateful for any help ou could provide.

    Merci bien!

    J'aime

  3. Mehdi

    Bonjour,

    Encore merci pour ce très bon tutoriel. J'ai un petit souci pour faire fonctionner le tri. Je récupère bien les differentes colonnes, mais je ne récupere pas les informations liées (data, orderable, search->regex, etc…). Ce qui fait que la fonction SortOrder ne fonctionne pas. J'ai pourtant reproduit exactement votre tutoriel, en reprenant les mêmes scripts. N'auriez-vous pas une petite idée de ce qui pourrait se passer?

    Merci d'avance.

    Cordialement,
    Mehdi MAMOU

    J'aime

  4. Marien Monnier

    Hi,

    Did you manage to make your changes or do you still need help?

    I would advise you to use Entity Framework to easily retrieve data from a Products table in SQL Server.

    J'aime

  5. Marien Monnier

    Bonjour,

    Quelle version de jQuery DataTables utilisez-vous ?
    Sinon, je vous invite à m'envoyer un exemple de solution minimaliste où vous reproduisez le problème par mail à m.monnier at softit dot fr

    J'aime

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s