Convertir un projet ASP.NET MVC 3 en MVC 4

2013-03-27 #mvc

J'ai migré mon application Répertoir vers ASP.NET MC 4. Comme le dit la doc :

The simplest way to upgrade is to create a new ASP.NET MVC 4 project and copy all the views, controllers, code, and content files from the existing MVC 3 project to the new project. Upgrading an ASP.NET MVC 3 Project to ASP.NET MVC 4

Créer un projet ASP.NET MVC 4 réellement vide

Note : Le projet vide est déjà pas mal vide, mais il intègre Web API par défaut et je n'en ai pas l'utilité.

Créer un projet "Empty"

  • Renommer le dossier C:\MVC\Repertoir en C:\MVC\Repertoir3
  • Sous Visual Studio 2010, créer un nouveau projet "Repertoir" de type "ASP.NET MVC 4 Web Application" dans le dossier "C:\MVC"
  • Sélectionner le template "Empty" avec bien évidemment :
    • Razor comme View Engine
    • Ok pour créer le projet de test unitaire

Supprimer Web API du projet

  • Pour le projet Repertoir : Références, clic-droit et "Gérer les packages NuGet..."
  • Désinstaller "Microsoft ASP.NET Web API" => signale que va aussi désinstaller :
    • Microsoft.AspNet.WebApi.WebHost
    • Microsoft.AspNet.WebApi.Core
    • Microsoft.AspNet.WebApi.Client
    • Newtonsoft.Json
    • Microsoft.Net.Http
  • Confirmer que c'est OK => il ne reste plus que :
    • Microsoft.AspNet.Mvc
    • Microsoft.AspNet.Razor
    • Microsoft.AspNet.WebPages
    • Microsoft.Web.Infrastructure

Supprimer Web API du projet de test unitaire

  • Pour le projet Repertoir.Tests : Références, clic-droit et "Gérer les packages NuGet..."
  • Désinstaller "Microsoft.AspNet.WebApi.WebHost" => signale que va aussi désinstaller :
    • Microsoft.AspNet.WebApi.Core
    • Microsoft.AspNet.WebApi.Client
    • Newtonsoft.Json
    • Microsoft.Net.Http
  • Confirmer que c'est OK => il ne reste plus que :
    • Microsoft.AspNet.Mvc
    • Microsoft.AspNet.Razor
    • Microsoft.AspNet.WebPages
    • Microsoft.Web.Infrastructure

Finaliser le projet vide

  • Dans le projet Repertoir / App_Start, supprimer le fichier WebApiConfig.cs
  • Dans Repertoir / Global.asax.cs, supprimer la ligne WebApiConfig.Register(GlobalConfiguration.Configuration); dans Application_Start().
  • Vérifier que tout est OK :
    • Fichier / Enregistrer tout
    • Générer / Regénérer la solution
    • => La regénération globale a réussi

Ajouter quelques packages

Avant de commencer, faire un clic-droit sur la solution "Repertoir" et sélectionner "Activer la restauration du package NuGet".

Ensuite, faire clic-droit sur Repertoir /Références et "Gérer les packages NuGet..." pour installer les packages dont j'ai besoin :

  • AutoMapper
  • EntityFramework
  • jQuery
  • jQuery.Validation
  • LowercaseRoutesMVC (pas LowercaseRoutesMVC4 puisque Web API a été éjecté)
  • Microsoft.jQuery.Unobtrusive.Validation
  • MiniProfiler
  • MiniProfiler.EF
  • Modernizr

Puis clic-droit sur Repertoir.Tests /Références et "Gérer les packages NuGet..." pour installer les packages suivants :

  • EntityFramework
  • Moq
  • MvcRouteUnitTester

Vérifier que tout est OK :

  • Fichier / Enregistrer tout
  • Générer / Regénérer la solution
  • => La regénération globale a réussi

Copier les sources du projet MVC 3 vers MVC 4

Dans le cas du projet Repertoir, cela consiste à copier :

  • le contenu de /App_Data
  • le répertoire /Contents
  • le contenu de /Controllers
  • le répertoire /Helpers
  • le contenu de /Models
  • le fichier /Scripts/chosen.jquery.fr.js
  • le fichier /Scripts/gmaps.js
  • le contenu de /Views (à l'exception du Web.Config)

Et ne pas oublier d'inclure ces différents fichiers dans le projet (à l'exception des fichiers contenus dans /App_Data)

Pour le projet Repertoir.Tests, il faut copier :

  • le répertoire /Controllers
  • le répertoire /Helpers
  • le fichier /RoutesTest.cs

Là aussi, penser à inclure ces nouveaux fichiers dans le projet.

Mettre à jour les fichiers de configuration

  • Repertoir/Web.Config :
    • recopier la section <connectionStrings>
    • <customErrors mode="RemoteOnly" defaultRedirect="~/Error" />
    • Les sections <authentication>, <membership>, <profile> et <roleManager> sont absentes (sans doute parce que je suis parti d'un template vide) mais je n'en ai pas besoin.
  • Repertoir/Web.Release.Config : recopier celui de Repertoir3 (pour la transformation lors de la mise en production sur Appharbor)
  • Repertoir/Views/Web.Config : ajouter <add namespace="Repertoir.Helpers" /> à <system.web.webPages.razor>
  • Repertoir.Tests/App.Config : recopier la section <connectionStrings>

Adapter le Global.asax

using System;
using System.Web.Mvc;
using System.Web.Routing;
using Repertoir.Helpers;
using Repertoir.Models;
using StackExchange.Profiling;

namespace Repertoir
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            ModelBinders.Binders.Add(typeof(string), new StringModelBinder());

            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(new RazorViewEngine());

            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            AutoMapperConfiguration.Configure();

            MiniProfilerEF.Initialize(true);
        }

        protected void Application_BeginRequest(object sender, EventArgs e)
        {
            if (Request.IsLocal) MiniProfiler.Start();
        }

        protected void Application_EndRequest(object sender, EventArgs e)
        {
            MiniProfiler.Stop();
        }
    }
}

Mise au point des routes

Il faut également adapter /App_Start/RouteConfig.cs pour tenir compte de la route "Id_Slug" et du fait que j'utilise LowercaseRoutesMVC.

using System.Web.Mvc;
using System.Web.Routing;
using LowercaseRoutesMVC;

namespace Repertoir
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRouteLowercase(
                name: "Id_Slug",
                url: "{controller}/{action}/{id}/{slug}",
                defaults: new { controller = "Contacts", action = "Index" }
            );

            routes.MapRouteLowercase(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Contacts", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

Vérifier que tout est OK

  • Fichier / Enregistrer tout
  • Générer / Regénérer la solution

=> 5 erreurs "Repertoir.MvcApplication ne contient pas de définition pour RegisterRoutes" dans le projet Repertoir.Tests

Remplacer 5 fois la ligne :

MvcApplication.RegisterRoutes(routes);

par :

RouteConfig.RegisterRoutes(routes);

Revérifier que tout est OK

  • Fichier / Enregistrer tout
  • Générer / Regénérer la solution

=> La regénération globale a réussi

Lancer les tests unitaires

Test / Exécuter / Tous les tests de la solution

=> 2 erreurs sur 151 tests

Les deux tests TestIncomingRoutes et TestOutgoingRoutes lèvent une exception System.InvalidOperationException parce que la classe Repertoir.MvcApplication n'a pas de méthode RegisterRoutes. Problème un peu plus compliqué, mais pas insoluble. Là encore, il faut remplacer la ligne :

var tester = new RouteTester<MvcApplication>();

par :

var routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes);
var tester = new RouteTester(routes);

Test / Exécuter / Tous les tests de la solution

=> 151/151 réussi(s)

Lancer l'application

Boum !

L'exception SqlCeException n'a pas été gérée par le code utilisateur

The column name is not valid. [ Node name (if any) = c,Column name = CreatedOn ]

Zut ! J'avais complètement oublié ça : SqlException on EF 5 w/ .NET 4.5 => Entity Framework 5 expects CreatedOn column from MigrationHistory table.

Mais moi j'utilise SQL Server CE, alors ça le fait pas avec le truc pour "System.Data.SqlClient.SqlException". Sous Visual Studio, il faut donc aller dans Debug / Windows / Exception Settings. Dans l'entrée "Common Language Runtime Exceptions", il faut ajouter l'exception "System.Data.SqlServerCe.SqlCeException" et ne pas cocher la case "Break When Thrown" puis clic-droit dessus pour vérifier que l'action "Continue when unhandled in user code" n'est pas activée.

Et enfin tout marche !!!!

Quoique. Pour que le déploiement fonctionne sur AppHarbor, il faut s'assurer que le Build Action des 2 fichiers "Web.Debug.config" et "Web.Release.config" est bien à "Content" (et pas à "None").

Rebrancher Git

C'est là que la magie opère : copier le dossier ".git" et les fichiers ".gitattributes", ".gitignore" et "readme.md" de l'ancien projet C:\MVC\Repertoir3 vers le nouveau projet dans C:\MVC\Repertoir. C'est pas avec SourceSafe qu'on pourrait jouer à des trucs pareils...

Puis lancer Github for Windows et commiter "Migration ASP.NET MVC 4".