Fermer

juin 11, 2021

Migrer vers ASP.NET Core MVC depuis ASP.NET Framework MVCUne minute de lecture



La migration d'une application ASP.NET MVC de .NET Framework vers ASP.NET Core peut être simple ou délicate : chaque projet est différent. Je vais vous expliquer une façon de migrer et vous montrer quelques pièges que j'ai rencontrés lors de la migration d'applications.

. NET existe depuis un certain temps et a eu de nombreuses versions. Nous avons commencé le .NET Framework, sommes passés à .NET Core, et maintenant juste .NET, en quelque sorte. 😄

Maintenir votre application à jour avec la dernière version de .NET peut être un défi. Il y a des budgets à considérer, du temps pour apprendre les différences et du temps pour « s'intégrer dans la migration ». La migration ou la « mise à niveau » d'un MVC ASP.NET de .NET Framework vers ASP.NET Core peut parfois être facile. D'autres fois, cela peut être juste difficile. Il n'y a pas de solution miracle à la migration, car chaque projet et chaque solution sont différents.

Je vais vous expliquer l'une des manières dont vous pouvez réussir la migration d'ASP.NET MVC vers ASP.NET Core MVC. À la fin de l'article, je vais vous montrer quelques pièges que j'ai rencontrés sur des applications de migration.

Mise en route

Pour cet article, je suppose que nous travaillons sur un ASP.NET MVC (.NET Framework) qui est une solution de projet unique, ce qui signifie que l'accès aux données, la logique métier, les modèles, etc., sont tous dans une seule solution. Similaire à ceci.

Ce projet unique est une application ASP.NET MVC écrite avec .NET Framework 4.5.2-4.8. Il existe une dépendance de données SQL Server où la base de données est présente dans le dossier App_Data. L'accès aux données est géré via EntityFramework. Vous pouvez trouver un référentiel de projet terminé ainsi que les instructions de configuration de la base de données sur GitHub.

Microsoft a facilité la création d'un auto- application contenue et combiner l'interface utilisateur avec la base de données et toute logique métier dont vous avez besoin. Cependant, avec l'application étroitement couplée, ce style rend difficile la migration ou la mise à niveau ou même le test de votre application. Notre approche sera de diviser l'application en couche différente rs ou responsabilités, comme l'interface utilisateur, la couche de données/référentiel et la couche métier/service.

Bien qu'il existe l'.NET Upgrade Assistant pour vous aider, il est toujours en préversion et ne fait qu'une partie des travail de jambes pour vous. Dave Brock a rédigé un joli post sur son utilisation. Je vais vous expliquer certaines des étapes pour reconcevoir votre application afin de faciliter un peu cette mise à jour et d'autres mises à jour. Espérons que cela n'arrive pas. 😄

Séparez les modèles

La première étape de la migration consiste à placer votre domaine ou vos objets de transfert de données dans un projet distinct. Le fait d'avoir vos objets de domaine, tels que Client, Commande, etc., dans une bibliothèque distincte vous permet de commencer à diviser votre application en couches. Cette couche de domaine avec tous les modèles qui décrivent vos données/objets sera utilisée tout au long de la nouvelle solution pour communiquer des données entre les couches.

En supposant que vous utilisiez Entity Framework pour accéder à votre base de données avec le code-based développement de modèle et non basé sur EDMX.

Si votre application utilise l'approche basée sur EDMXsuivez l'approche Portage d'un modèle basé sur EF6 EDMX pour Guide EF Core pour la mise à jour vers l'approche de modèle basée sur le code. À l'avenir avec EntityFramework Core, les modèles basés sur EDMX ne sont pas utilisés.

La première chose que vous voudrez faire est de créer une nouvelle bibliothèque de classes ciblant .NET Standard. Pourquoi .NET Standard et pas seulement .NET ? Avoir les bibliothèques partagées comme les bibliothèques de domaine ou de données dans .NET Standard vous permet une plus grande portabilité entre les projets et les plates-formes. Cette approche vous permettra également de migrer lentement des parties du projet principal tout en le maintenant.

Déplacez maintenant ces classes de modèle vers le nouveau projet. Je l'appellerais quelque chose comme Contacts.Domain. Je place généralement tous les modèles dans un dossier Models.

L'explorateur de modèles de contact affiche : Contacts.Domain with Dependencies, Interfaces and Models. Sous Modèles se trouvent Address.cs, AddressType.cs, Contact.cs, Phone.cs, PhoneType.cs.

Vous voudrez ajouter une référence à la nouvelle bibliothèque Contacts.Domain à l'application Contacts existante. N'oubliez pas de mettre à jour les instructions using !

Remarque : Lorsque vous déplacez des classes/fichiers entre des dossiers, des espaces de noms ou des projets, utilisez la refactorisation Move Instance Method ( Visual Studio ou JetBrain Rider/Resharper).

Séparer la couche de données

Nous allons maintenant travailler sur l'obtention de méthodes d'accès aux données à partir de l'interface utilisateur (application Web). Tout d'abord, nous souhaitons créer une nouvelle bibliothèque de classes ciblant .NET Standard et ajouter une référence à EntityFrameworkCore. La partie suivante peut être difficile, selon la configuration de votre application.

Je suppose que la plupart des accès aux données pour votre application dans les méthodes du contrôleur ressemblent à ceci.

public ActionResults Index() {
    var _db = new Contact.ContactsContext() ;

    var contacts = _db.Contacts.ToList();[19659033]retour Afficher(contacts);
}

ou

public ActionRésultats Index()  {
 
    using (var _db = new Contact.ContactsContext()[19659024]) 
    {
        var contacts = _db .Contacts.ÀListe();

        retour Afficher(contacts)[19659024];
    }
}

La manière dont vous créez la couche de données dépend maintenant de vous. Je suis généralement le modèle du gestionnaire ou du référentiel. Il existe de nombreux modèles de conception que vous pouvez suivre. Le choix vous appartient et non l'intention de cet article de blog. L'objectif est d'avoir une ou plusieurs classes chargées de gérer l'enregistrement, la mise à jour, la suppression et l'interrogation des données pour l'interface utilisateur.

Créer le contexte de base de données EntityFramework  :

namespace .Données
{
    public class ContactContext :DbContext
    {
        private lecture seule IConfiguration_configuration;
        public ContactContext(IConfiguration configuration)
        {
            _configuration = configuration;
        }
        public DbSet<Contact> Contacts { get[19659024]; set; }
        public DbSet<Adresse> Adresses { get ; set; }
        public DbSet<Phone> Téléphones { get;[19659028]set; }
        public DbSet<AddressType> AddressTypes { get;  set; }
        public DbSet<PhoneType> PhoneTypes { get; set[19659024]; }

        protégé outrepasser void OnConfigurer(DbContextOptionsBui options de lder)
            => options.UseSqlServer(_configuration.GetConnectionString([19659123)ContactsDatabaseSqlServer"));
    }
}

Voici un exemple de ce à quoi pourrait ressembler la classe ContactRepository.

namespace Contacts.Données
{
    public class ContactRepository 
    {

        public Domain.Contact GetContact(int ] contactId) 
        {
            using (var _db = new Contacts.Data. ContactContext())
            {
                return _db.Contacts.(c => c.ContactId == contactId);
            }
        }
        
     }
}

Une fois que vous avez déplacé tous les accès aux données de l'interface utilisateur précédente vers le nouveau projet de données, vous devriez pouvoir remplacer vos appels de base de données par Data.nom de la méthodecomme Contacts.Data.GetContact(contactId) en utilisant l'exemple ci-dessus.

Cette application roach peut sembler un peu risqué ou effrayant puisque vous continuez à remplacer des parties de votre application. Je mentirais si je disais que ce n'était pas risqué et effrayant. La vérité est que c'est risqué et effrayant. Cependant, vous pouvez atténuer certains des risques et faciliter les modifications futures. Ai-je déjà éveillé votre intérêt ? C'est là qu'interviennent les tests unitaires.

Mais avant de pouvoir construire nos tests unitaires, nous devrons travailler sur notre solution pour permettre la moquerie de nos classes de référentiel de données. Non, pas dérivé d'eux, mais d'eux. Mocking complète les frameworks de tests unitaires en isolant les dépendances en créant des objets de remplacement. Dans notre exemple, nous allons nous moquer ou « simuler » nos appels de base de données.

Pour nous moquer de notre référentiel, nous devrons créer une interface pour le référentiel afin que la plupart des frameworks moqueurs puissent créer les objets correspondants.

Remarque : Si vous utilisez un framework de test/mocking commercial tel que Telerik JustMockvous n'avez pas besoin de créer l'interface. Cela fonctionne. Ils prennent même en charge les mocking classes EntityFramework.

La création de l'interface pour la bibliothèque de données nouvellement créée peut être effectuée de deux manières, manuellement ou automatiquement. Je recommande la méthode automatique qui consiste à sélectionner le nom de la classe, à cliquer et à choisir 'Refactor' | « Extraire l'interface ». Assurez-vous de placer les interfaces dans la même bibliothèque de classes que les modèles.

L'interface ressemblera à ceci.

namespace Contacts.Domain.Interfaces
{
    public interface IContactRepository
    {
        Contact GetContact(int contactId);
        
    }
}  

Créer une suite de tests unitaires

Je n'ai pas l'intention de cette section être une présentation approfondie des tests unitaires. Je ne couvrirai pas tous les scénarios possibles que vous devriez ou ne devriez pas couvrir. Le nombre de tests unitaires et leur complexité sont plus un art qu'une science. Lors de la création de tests unitaires, j'essaie de couvrir le chemin heureuxle chemin d'exception et le chemin malheureux. Est-ce que ça marche comme c'est censé le faire ? Dois-je gérer les exceptions connues et courantes ? Est-ce que je gère aucune/mauvaise entrée de données commune ? Mais encore une fois, votre kilométrage peut varier.

Voici un exemple des tests unitaires GetContact

[Fact]
public void GetContact_WithAnInvalidId_ShouldReturnNull ()
{
    
    var mockContactRepository = new Mock<IContactRepository>([19659024]);
    mockContactRepository.Configuration(contactRepository =>
        contactRepository.ObtenirContact(It.IsInRange(int.MinValue, 0[19659024], Gamme.Inclus))
    ).Retours<Contact> (null);

    var contactManager = new ContactManager(mockContactRepository.Objet);

    
    var contact = contactManager.GetContact(-1) ;

    
    Affirmer.Null(contact);
}

[Fait]
public void[19659039]GetContact_WithAValidId_ShouldReturnContact()
{
    
    var mockContactRepository = new Mock[1945249004][194590Repository] ();
    mockContactRepository.Configuration(contactRepository =>
        contactRepository.ObtenirContact(It.IsInRange(1, int.MaxValue, Gamme.Inclus))
    ).Retours((int contactId) => nouveau Contact
    {
        Id de contact = Id de contact
    });

    var contactManager = new ContactManager(mock ContactRepository.Object);
    const int demandéContactId = 1;

    
    
    var contact = contactManager. Obtenir le contact(Id de contact demandé);

    
    Affirmer.NotNull(contact);
    Assert.Equal(requestedContactId, contact.ContactId);
}

Créer un Nouvelle application Web

Oui, je l'ai dit, créez une nouvelle application Web. Cependant, ce ne sera pas aussi difficile que cela puisse paraître. Nous allons créer le nouveau projet à l'aide du modèle afin que la plupart du nouveau « code de plomberie » soit créé pour nous. Je vais parcourir les parties qui sont différentes. Étant donné que nous supposons que votre application a été écrite à l'aide d'ASP.NET MVC, assurez-vous de créer un nouveau projet et de choisir l'application Web ASP.NET Core avec le type "Modèle View Controller".

Conseil : Pendant que vous créez une nouvelle application Web, vous pouvez utiliser les modèles d'application qui font partie de la Telerik UI for ASP Core Component Suite de composants et de contrôles pour rendre votre développement beaucoup plus facile et plus rapide.

Nouvelle application Web – Rider

Nouvelle application Web – Visual Studio

Qu'est-ce qui est différent ?

Regardons la structure des dossiers et les nouveaux fichiers.

Dossiers

Les deux premiers dossiers de cet exemple sont les mêmes : Dependencies, Proper liens, modèles, services et vues. J'ai copié les modèles, les vues et les services de mon projet précédent.

Vous remarquerez qu'il manque un dossier : Content. C'est parce que les fichiers de Contentplus encore les fichiers statiques, ont été déplacés vers le nouveau dossier wwwroot. Vous trouverez ici les dossiers pour cssjslib et favicon.ico. L'idée est quelque chose qui ne change pas et ne fait pas partie des pages générées par ASP.NET ou la logique est placée dans le dossier wwwroot. Le contenu du dossier wwwroot est servi par rapport à la racine de l'application.

Donc, si mon application était https://www.josephguadagno.netn'importe quoi dans le wwwroot serait servi à partir de https://www.josephguadagno.net. Le favicon.ico serait servi à https://www.josephguadagno.net/favicon.ico. Vous pouvez donc déplacer vos images dans ce dossier. N'oubliez pas si vous déplacez vos images pour créer des règles de réécriture ou pour refléter le chemin dans lequel vous les aviez à l'origine. Il manque web.*.configpackage.config et global.asax. Le web.*.config a été remplacé par le appSetting.json—nous en parlerons plus tard. Le package.config a été déplacé "à l'intérieur" du fichier csproj. Le fichier global.asax a été principalement remplacé par le fichier Startup.cs. Il existe également de nouveaux fichiers : appsettings.*.jsonProgram.cs et Startup.cs.

Application Configuration

Au revoir web.config ! C'était amusant, mais vous étiez parfois désordonné et difficile à gérer. Bonjour appsettings.json. appsettings.json est le modèle de configuration d'application pour .NET et ASP.NET Core.

Une configuration d'application de démarrage « typique » ressemblerait à ceci.

{
  "ConnectionStrings" : {
    "ContactsDatabaseSqlServer": ""
  },
  "Logging": {
    "LogLevel ": {
      "Par défaut": "Informations",
      "Microsoft": "Avertissement",[19659319]"Microsoft.Hosting.Lifetime": "Informations"
    }
  },
  "Hosts autorisés": "*"
 }

Ici, nous définissons la chaîne de connexion de ContactsDatabaseSqlServer dans l'objet ConnectionStrings et définissons la journalisation pour l'application.

Vous remarquerez que, par défaut, il est un appsettings.json et un appsettings.Development.json. ASP.NET Core prend en charge la configuration par environnement. Il n'est plus nécessaire d'avoir à gérer les transformations web.config. Dans le fichier appsettings.Development.jsonajoutez simplement le paramètre que vous souhaitez outrepasser pour le développement. Dans cet exemple, je voudrais mettre à jour ma connexion à la base de données en cours de développement. Le appsettings.Development.json ressemblerait à ceci.

{
  "ConnectionStrings": {
    "ContactsDatabaseSqlServer": "" 
  }
}

Pour en savoir plus sur la configuration dans ASP.NET Core sur la page documentation.

Program.cs

Program.cs fonctionne exactement comme pour une console. Il sert de point d'entrée pour votre application. Pour la plupart, vous démarrez l'hébergeur Web.

en utilisant Microsoft.AspNetCore;
en utilisant Microsoft.AspNetCore.Hébergement;

espace de noms Contacts.WebUi
{
    public class Program
    {
        public static void Main(string[[19659024]] args)
        {
            CréerWebHostBuilder(args).Build(). Exécuter();
        }

        public static IWebHostBuilder CréerWebHostBuilder(string[ ]] arguments) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>( ]);
    }
}

Startup.cs

Le Startup.cs est l'endroit où vous configurez votre site. Les méthodes de la classe Startup informent le moteur d'hébergement des services que vous utilisez. ASP.NET Core a un modèle opt-ince qui signifie que vous lui dites ce que vous voulez. Dans les versions précédentes d'ASP.NET, le framework vous offrait tout. Il existe deux méthodes dans la classe Startup ; Configure et ConfigureServices.

Configure Method

La méthode Configure est utilisée pour configurer le pipeline http. Un exemple de méthode ressemble à ceci.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if[19659026](env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        
        app.UseHsts();
    }
    app.UtiliserHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.Utiliser l'autorisation();

    app.UseEndpoints(endpoints =>
    {
        points de terminaison.MapControllerRoute(
            nom : "par défaut",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
} 

Dans cette définition, vous verrez que nous cherchons à voir dans quel environnement nous exécutons l'affichage de l'erreur appropriée (lignes 3 à 12). Nous choisissons ensuite de rediriger toutes les demandes pour utiliser https (ligne 13), autoriser l'hôte à servir des fichiers statiques (ligne 14), utiliser le routage par défaut (ligne 16), utiliser l'autorisation (ligne 18) et enfin utiliser des points de terminaison pour MVC. Comme vous pouvez le voir, nous indiquons explicitement à ASP.NET Core et à l'hôte comment il doit fonctionner au lieu de faire des hypothèses.

ConfigureServices

ConfigureServices ] est utilisé pour informer ASP.NET des services que vous prévoyez d'utiliser. Le minimum pour une application ASP.NET Core MVC aurait services.AddControllersWithView(). Vous pouvez également enregistrer vos dépendances d'application, la journalisation, le contexte de la base de données, etc.

Gotchas

Voici quelques éléments qui m'ont déconcerté une ou deux fois lors de la migration d'ASP.NET Framework vers ASP.NET Core. Espérons que vous ne les voyez pas, mais si vous le faites, essayez ceci!

System.ComponentModel.DataAnnotation

La bibliothèque System.ComponentModel.DataAnnotation est cruciale dans Entity Framework. Cet espace de noms se trouvait dans l'assembly/package pour System.ComponentModel. À un moment donné de l'évolution de .NET Framework, au moins dans la version 4.7.2, System.ComponentModel.DataAnnotation a été déplacé dans son propre assembly/package. Cette modification ne vous affectera que si vous migrez vers ASP.NET Core MVC en maintenant le site .NET Framework ASP.NET MVC et en travaillant avec .NET Standard, comme je l'ai dit plus tôt.

Web.Config Target Framework[19659293]Certains fichiers web.config contiennent le targetFramework défini en plus du fichier csproj. Recherchez le nœud system.web dans la configuration de web.configassurez-vous que les nœuds compilation et httpRuntime ont le même targetFramework en tant que votre csproj.

Web.config Snippet

  <system.web>
    <compilation debug= "true" targetFramework="4.7.2" />
    <httpRuntime  targetFramework="4.7.2" />
  </system.web>

csproj Snippet

<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>

Utilisation de .NET Standard (Cibles de compilation)

Ce piège est applicable si vous utilisez .NET Standard pour faciliter la migration et continuez à travailler avec l'application .NET Framework MVC.

Une fois que vous exécutez un Application framework ASP.NET MVC avec une référence de bibliothèque à une bibliothèque écrite en standard .NET, comme le Contacts.Model projectvous pouvez voir une ou plusieurs erreurs. Dans Chrome ou Microsoft Edge, vous pouvez obtenir un message d'erreur de redirection « Illimité » ou « Trop nombreux ». Cette erreur se produira si vous avez des messages d'erreur personnalisés dans votre application.

<customErrors mode="On" defaultRedirect="ErrorPage.aspx?handler=customErrors">
    <error statusCode="404" redirect="ErrorPage.aspx?handler=customErrors" />
</customErrors>

Désactivez les erreurs personnalisées en modifiant l'attribut mode sur Off. Si vous actualisez le navigateur, vous verrez un message indiquant « System.Object is not found ». C'est un message étrange car System.Object fait partie à la fois d'ASP.NET Core et d'ASP.NET. Cependant, l'erreur résulte du référencement d'un projet .NET Standard et de l'absence de référence à .NET Standard dans l'application .NET Framework.

Après avoir ajouté la référence, réexécutez la solution. Il échouera encore. Un autre problème étrange, la raison de cet échec est que IIS ne sait pas comment charger cet assembly. Alors disons-lui comment le charger. Recherchez le nœud compilationassemblies dans votre web.config et ajoutez l'assembly.

<add assembly="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51"/>

Le nœud peut ressembler à ceci après avoir ajouté l'assemblage.

<compilation debug="true" targetFramework="4.7.2">
    <assemblages>
        <add assembly="netstandard, Version=2.0.0.0, Culture=neutral,PublicKeyToken=cc7b13ffcd2ddd51"/> 
    </assemblages>
</compilation>

Remarque : Vous pouvez avoir d'autres assemblys dans ce nœud en fonction de votre application.

Vous devez maintenant exécuter et afficher l'application. N'oubliez pas de réactiver vos erreurs personnalisées.

App_Data

ASP.NET Core n'a pas le concept d'un dossier App_Data utilisé dans les versions antérieures d'ASP.NET. App_Data a couramment utilisé la base de données d'identité ou la configuration d'application dynamique. Bien que vous deviez probablement stocker des bases de données ou des fichiers de base de données sur le serveur Web, il est courant sur les machines de développement d'avoir les bases de données spécifiques à l'application dans le dossier App_Data. Bien qu'ASP.NET Core ne le prenne pas en charge immédiatement, vous pouvez le faire avec un peu de code.

Le code de cette solution de contournement doit figurer dans la classe Startup.cs.

Tout d'abord, vous créez un jeton. ou une chaîne dans le fichier appsetting.development.json que nous remplacerons par le dossier dans lequel l'application s'exécute. Ici, vous verrez, j'ai ajouté le jeton %CONTENTROOTPATH% comme partie de la propriété AttachDbFilename. (Remarque : Le nom du jeton peut être ce que vous voulez.)

{
    "ConnectionStrings": {
        "ContactsSqlServer": "Source de données=(LocalDB)\MSSQLLocalDB;
        AttachDbFilename=%CONTENTROOTPATH%\App_Data\contacts.mdf;
        Sécurité intégrée=Vrai"
    }
}

Next, in the Startup.cs file, you need to create a variable to hold the path to the content.

private string _contentPath = "";

Next, you’ll need to update the constructor of the Startup class to have ASP.NET Core inject the configuration and web host environment.

private string _contentRootPath = "";
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
    Configuration = configuration;
    _contentRootPath = env.ContentRootPath;
}

Then in the ConfigureServicesbefore you need to use the App_Data folder.

string connectionString = Configuration.GetConnectionString("ContactsSqlServer");
if (connectionString.Contains("%CONTENTROOTPATH%"))
{
    connectionString = connectionString.Replace("%CONTENTROOTPATH%", _contentRootPath);
}

When you add the Db Context in ConfigureServicesreplace the code with.

services.AddDbContext<Data.ContactsContext>(options => {  options.UseSqlServer(connectionString);});

Now copy App_Data folder from your previous project to the new one.

Identity

If you used identity management in ASP.NET MVC Framework, you need to update a couple of things, primarily if you used Entity Framework to assist.

In ASP.NET MVC, authentication and identity features are configured using ASP.NET Identity in Startup.Auth.cs and IdentityConfig.cslocated in the App_Start folder. In ASP.NET Core MVC, these features are configured in Startup.cs.

Install the following NuGet packages:

  • Microsoft.AspNetCore.Identity.EntityFrameworkCore
  • Microsoft.AspNetCore.Authentication.Cookies
  • Microsoft.EntityFrameworkCore.SqlServer

Then you’ll need to configure identity in the Startup.ConfigureServices method of Startup.cs. Something like this:

public void ConfigureServices(IServiceCollection services)
{
    
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

     services.AddMvc();
}

You can read more on it at Migrate Authentication and Identity to ASP.NET Core or ASP.NET Core Identity 3.0 : Modifying the Identity Database

Wrap-up

That’s it! That’s a lot to take. While I can’t cover every possible scenario that you might hit, hopefully you have enough to get you started and handle some of the surprises that I ran into while migrating applications.




Source link

0 Partages