[ASP.NET MVC 4+] Erreur 403 ou 404 après bundling/minification

Avec la version 4.5 d’ASP.NET, Microsoft a intégré une fonctionnalité relativement intéressante : le bundling.

Bundling

Le bundling permet de combiner ou d’empaqueter plusieurs fichiers CSS ou JavaScript en un seul fichier. Il s’agit alors d’un simple bundle qui contiendra tous les fichiers de la liste, permettant ainsi d’accélérer le chargement des pages : moins de fichiers signifie moins de requêtes HTTP.

Exemple de bundles :

bundles.Add(new ScriptBundle("~/bundles/site").Include(
"~/Scripts/noty/packaged/jquery.noty.packaged.min.js",
"~/Scripts/softit.utils.js",
"~/Scripts/main.js"));
 
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.css",
"~/Content/site.css"));
De plus, il est assez intelligent pour que, lorsque votre site est compilé en release, seuls les fichiers JS/CSS minifiés (.min) soient utilisés. Par exemple, si j’ai un fichier bootstrap.js et un fichier bootstrap.min.js. Et j’ai un bundle sur bootstrap.js. Lorsque je compile en Debug il utilise boostrap.js; lorsque je compile en Release, il utilise bootstrap.min.js

Lire la suite

L’équipe Soft’it sera présente aux Microsoft TechDays 2015 : entre nouvelle ère et nouvelles technologies !

Quoi ? Vous n’avez jamais entendu parler des Techdays ? Non mais allo quoi ! Vous êtes des développeurs et vous n’avez jamais entendu parler de cet événement incontournable pour tout expert Microsoft !
Si tel est le cas, vous étiez sans doute sur une autre planète…

Une petite vidéo s’impose donc :

En effet, les TechDays de Microsoft (le 10, 11 et 12 février au Palais des Congrès, à Paris), donnent le ton sur l’avenir des nouvelles technologies tout en offrant également la possibilité de voir des démonstrations de nouveaux outils ou concepts aussi bien pour les développeurs que pour les décideurs.

Cette année, le thème –Ambiant Intelligence– sera principalement accès sur :

  • La mobilité
  • Le cloud
  • La Big Data
  • Le marchine Learning
  • Les objets connectés

Dès ce mardi (plus destiné au développeur), l’équipe de Soft’it va participer à un certain nombre de sessions techniques notamment sur des sujets encore inédits comme l’ASP .NET 5, l’Entity framework 7 ou bien encore les nouveautés d’ASP .NET MVC 6.

Par ailleurs, l’équipe tentera de vous faire participer au maximum à cet événement au travers de tweets, et plusieurs articles seront publiés dans les jours à venir.
Les membres de Soft’it présents seront (de gauche à droite – cliquez sur les noms pour accéder à leur Twitter): Marien Monnier, Pier-Lionel Sgard, Jérôme Veneziani, Philippe Gung, Laurent Jacques, Ikbal Benakila, Rémi Lesieur-Bridel, Cédric Burceaux, Emmanuelle Aboaf et Philippe Beroucry.

Normal
0

21

false
false
false

FR
X-NONE
X-NONE

/* Style Definitions */
table.MsoNormalTable
{mso-style-name: »Tableau Normal »;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent: » »;
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family: »Calibri »,sans-serif;
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-fareast-language:EN-US;}

Enfin, pour cette année 2015, une grande nouvelle nouveauté ! 

Les TechDays, pour la première fois, seront accessibles aux
personnes sourdes et malentendantes via la vélotypie
.
La vélotypie est un
système de sous-titrage en temps réel projeté sur l’écran afin que tous
puissent lire ce qui est dit. Cela profite, non seulement aux personnes sourdes
et malentendantes, mais aussi à tous ceux qui n’arrivent pas à suivre ce qui
est dit pendant les conférences. 

Une des membres de l’équipe Soft’it, Emmanuelle Aboaf,
sourde de naissance, pourra bénéficier de ce système et suivre comme
tout le monde les conférences.
Soft’it remercie David Rousset et Microsoft
pour cette mise en place et salue l’initiative de rendre accessible l’événement
!

Vous trouverez une liste de sessions accessibles sur ce lien
http://blogs.msdn.com/b/davrous/archive/2015/02/03/liste-des-sessions-techdays-2015-accessibles-aux-sourds-et-malentendants.aspx

N’oubliez donc pas de revenir sur le blog pour avoir nos retours et vous faire partager au mieux cet événement incontournable.


window.twttr=(function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],t=window.twttr||{};if(d.getElementById(id))return;js=d.createElement(s);js.id=id;js.src= »https://platform.twitter.com/widgets.js »;fjs.parentNode.insertBefore(js,fjs);t._e=[];t.ready=function(f){t._e.push(f);};return t;}(document, »script », »twitter-wjs »));

[Témoignage] Voluntis : Comment passer du WebForms au MVC sans risque : ASP.NET MVC & Testing

Suite à notre récente formation sur le passage de l’ASP.NET WebForms à l’ASP.NET MVC en exploitant le testing, notre client, Erwan de Cadoudal (Team leader chez Voluntis [éditeur de logiciels dans le domaine médical], et demandeur de cette formation), a eu l’amabilité de répondre à nos questions.

Soft’it – Quelles sont vos motivations à l’origine de ce besoin de formation MVC et Testing MVC ?

E. de Cadoudal : « Voluntis met en œuvre des applications médicales dont le code peut présenter un risque pour les patients. Par exemple nos solutions mettent en œuvre des algorithmes médicaux complexes qui permettent d’aider le patient dans sa décision thérapeutique.

Les solutions de Voluntis sont au carrefour du numérique et du thérapeutique. Dans ce contexte, MVC nous parait une solution moderne et efficace pour des déploiements dans de multiples contextes d’usage, depuis le smartphone du patient jusqu’à l’écran du médecin. Par ailleurs nous sommes convaincus que l’approche MVC et les tests unitaires sont des solutions aujourd’hui très efficaces et très pertinentes pour développer rapidement des solutions industrialisées dans le contexte très réglementé des dispositifs médicaux. »

Soft’it – De quelle manière Soft’it y a répondu ? Qu’est-ce qui vous a plu dans la réponse de Soft’it à votre besoin ?

E. de Cadoudal : « L’approche de Soft’it nous a convaincu car le plan proposé a été était fait sur mesure avec une prise en compte de nos besoins et de nos équipes. La décision de suivre la formation dans nos locaux pour un nombre significatif de développeurs a été déterminante.
De plus certains exemples illustrant les exercices étaient proches de notre métier et nous parlaient bien. Par exemple pour nous il est plus clair de parler de liste de patients que de liste de bons de commande. Bien que cette session nous a paru un peu trop concentrée, chacun d’entre nous est reparti avec bases communes et des exemples concrets : du code et des présentations.

Nous avons également pu apprécier le dynamisme, l’expertise et le professionnalisme des équipes de formation, qui ont permis de conserver un bon rythme et de faciliter des échanges constructifs, et ont été des facteurs clés du succès des formations que nous avons suivies.« 

Soft’it – Quels sont les résultats et/ou quelle dynamique cette approche a-t-elle amené ?

E. de Cadoudal : « L’intégration des patterns « MVC », initiés par nos équipes de R&D en 2014, devient le modèle de référence pour les nouveaux projets de Voluntis. La formation nous a permis de mieux appréhender cette technologie et ses impacts sur notre méthodologie de travail.

En parallèle, nous travaillons en étroite collaboration avec nos équipes de tests et validation pour augmenter la couverture de notre code par du test unitaire. Nous anticipons que l’automatisation de ceux-ci, couplée à nos méthodologies Agile, nous permettra de réduire significativement notre investissement de tests tout en améliorant la qualité de nos produits. »

Soft’it – Nous recommanderiez-vous ? Comptez-vous refaire appel à nous ?

E. de Cadoudal : « Seulement une partie des développeurs a suivi la formation, nous comptons remonter une session identique à la précédente.
L’approche de Soft’it a été la bonne dans notre contexte et je recommande la formule pour des équipes techniques qui travaillent sur un framework partagé avec les développeurs. »

 

Un grand merci à Erwan pour ces réponses et ce feedback positif. L’équipe ayant suivie la formation a par ailleurs donné un 4/4 au ROTI pour Marien et Rémi !

L’équipe Soft’it se plie donc en quatre pour vous fournir les meilleures formations adaptées à vos besoins.
Contactez-nous si vous souhaitez avoir plus de renseignements.

[Formation] Comment passer du WebForms au MVC sans risque : ASP.NET MVC & Testing

L’ASP.NET MVC est la technologie web .NET depuis plusieurs années maintenant. Cette plateforme nous permet de mieux maîtriser le rendu HTML, le découplage métier/interface et surtout le testing.

Voici donc 2 sujets bien connus de l’équipe et qui tiennent à cœur à Soft’it : les applications web (ASP.NET MVC) et la qualité (testing).

Un de nos clients, Voluntis (éditeur de logiciels dans le domaine médical), a eu une problématique que grand nombre de sociétés rencontrent actuellement : comment passer de l’ASP.NET WebForms à l’ASP.NET MVC sans régression, et sans impacter les utilisateurs.

Même si une application WebForms fonctionne correctement depuis des années, il y a un moment où la dette technique devient un frein aux améliorations et vous coûte plus cher que les évolutions ne peuvent vous apporter.
Nous l’avons constaté avec tous nos clients, il est nécessaire de toujours prendre le temps de rester « aux goûts du jour » afin d’être en mesure de toujours proposer des nouveautés, et surtout de ne pas décourager vos développeurs en travaillant sur des technos « moins sexy ».

L’un des gros avantages du MVC est la facilité à appliquer du test sur la plupart des couches de votre application (Modèle, Contrôleur, voire Vue). Pourquoi ne pas utiliser cet avantage précis pour migrer sans risque ?!

formation-mvc-voluntisC’est sur cette base que nous avons construit une formation sur-mesure pour 10 développeurs/architectes de chez Voluntis :

  • jour 1 : introduction à l’ASP.NET MVC et aux bonnes pratiques, puis exemples de passage du WebForms au MVC
  • jour 2 : introduction à la notion de testing et comment tester une application MVC

La pratique étant le meilleur moyen d’apprendre, chaque jour fut composé en moyenne de 40% d’atelier !

Pour remplir cette mission, nous avons envoyé 2 membres de l’équipe Soft’it :

Marien Monnier
Rémi Lesieur-Bridel

Le résultat : une formation sur-mesure calée aux besoins de Voluntis, un travail exemplaire de Marien et Rémi, une équipe très satisfaite (4/4 au ROTI pour tout le monde) et un client ravi :

De plus, Erwan de Cadoudal (Team leader chez Voluntis, et demandeur de cette formation), nous a fait un témoignage très positif.

Si vous souhaitez faire une formation technique et/ou qualité sur-mesure, contactez-nous.

[ASP.NET MVC] Requêtes GET très longues sur arterySignalR (Browser Link)

Récemment, lors du développement d’un site ASP.NET MVC 5 interrogeant une Web API 2.2 OData, nous avons constaté que nos processeurs tournaient à 100% lorsque nous exécutions le site en local.
Le problème ne se posait pas en debug, ni sur les plateformes d’intégration mais uniquement sur des sites IIS locaux.

En étudiant la trace Réseau avec les outils développeurs de Chrome et IE, nous avons remarqué qu’il y avait des milliers de requêtes GET sur http://localhost:<port>/<guid>/arterySignalR/send?transport=longPolling

IE
arterysignalr-IE

Chrome
arterysignalr-Chrome

Il s’avère que « arterySignalR » n’est autre que l’appel à Browser Link. C’est une fonctionnalité de Visual Studio 2013 permettant d’établir une connexion temps-réelle avec l’ensemble des navigateurs exécutant une application actuellement ouverte dans VS.
Toutes les interactions que vous faites dans votre code peuvent être répercutées via Visual Studio sur tous les navigateurs ouverts sur l’application en question.
Cela vous permet aussi de rafraîchir automatiquement vos feuilles de styles par exemple, sans avoir à faire un F5 sur votre navigateur.

Il s’avère que dans notre cas, l’appel à notre Web API derrière le site MVC plombait les perfs. Il a été plus judicieux de le désactiver en attendant de trouver une solution de contournement.

Si vous désirez faire de même, il vous suffit dans Visual Studio de cliquer sur le bouton de rafraîchissement à côté du bouton de Debug en navigateur, puis de décocher « Activer le lien de navigateur » :

arterysignalr-VS

Voilà !

En espérant que ça puisse vous aider autant que nous.

Source : http://stackoverflow.com/questions/19917595/net-localhost-website-consistantly-making-get-arterysignalr-polltransport-long

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

[MVC] Corriger les liens CSS relatifs dans un StyleBundle

Récemment, pour un nouveau projet en MVC 5, nous avons utilisé Bootstrap et jQuery DataTables.

Afin de mieux s’intégrer à « l’esprit » Boostrap, DataTables intègre un fichier JavaScript (dataTables.bootstrap.js) et un fichier CSS (dataTables.bootstrap.css).

Nous avions décidé d’ajouter ce fichier CSS à un StyleBundle dans BundleConfig.RegisterBundles :

bundles.Add(new StyleBundle("~/Content/bootstrap-datatable").Include(
            "~/Content/DataTables-1.10.0/css/dataTables.bootstrap.css"));

En local tout fonctionnait correctement, mais une fois sur le serveur d’intégration, les images de tri de DataTables n’apparaissaient pas. En regardant avec l’outil de développeur de Chrome, on peut voir que nous avions des erreurs 404 sur nos images :

Failed to load resource: the server responded with a status of 404 (Not Found) http://xxx/images/sort_desc.png

Après analyses, nous avons détecté que dataTables.bootstrap.css spécifie des chemins relatifs pour ses images :

table.table thead .sorting { backgroundurl('../images/sort_both.png') no-repeat center right; }
table.table thead .sorting_asc { backgroundurl('../images/sort_asc.png') no-repeat center right; }
table.table thead .sorting_desc { backgroundurl('../images/sort_desc.png') no-repeat center right; }

Le navigateur Web recherche donc les images en relatif par rapport au chemin de notre Bundle (~/Content/boostrap-datatable) et non pas en fonction du chemin relatif du fichier css (~/Content/DataTables-1.10.0/css/dataTables.boostrap.css).

Pour régler ce problème, la solution est très simple : il suffit de passer un IItemTransform à votre StyleBundle, et plus spécifiquement, le CssRewriteUrlTransform qui réécrit automatiquement les URLs des fichiers CSS inclus dans le StyleBundle.
Il vous suffit juste d’ajouter le CssRewriteUrlTransform à votre Include:

bundles.Add(new StyleBundle("~/Content/bootstrap-datatable").Include(
  "~/Content/DataTables-1.10.0/css/dataTables.bootstrap.css"new CssRewriteUrlTransform()));

A présent, nos images ont réapparu, et nous n’avons plus d’erreur 404. En analysant le CSS généré avec Chrome, on peut voir que les URLs du bundle ont été réécrites en fonction du chemin du fichier CSS :

table.table thead .sorting_desc {
    backgroundurl(/Content/DataTables-1.10.0/images/sort_desc.png) no-repeat center right;
}

Edit du 1er octobre 2014 :

Si vous utilisez une application dans un virtual directory (autre que dans la racine /), cette solution ne fonctionne pas. En effet, il n’ajoute pas le préfixe de l’application.

Dans ce cas, vous devez créer votre propre IItemTransform tout en utilisant CssRewriteUrlTransform auquel vous fournissez le chemin corrigé :

public class CssRewriteUrlTransformWrapper : IItemTransform
{
    public string Process(string includedVirtualPath, string input)
    {
        return new CssRewriteUrlTransform().Process("~" + VirtualPathUtility.ToAbsolute(includedVirtualPath), input);
    }
}

Source : http://aspnetoptimization.codeplex.com/workitem/83