#asp-net-core#mvc#crud#mysql#entity-framework#dotnet#c-sharp#visual-studio

CRUD ASP.NET Core MVC avec MYSQL

Apprenez les opérations CRUD sur ASP.NET Core MVC avec MySQL. Tutorial complet avec Entity Framework Core, Data Seed et déploiement en production.

Dans cet article, nous allons apprendre les opérations CRUD (Create Read Update Delete) sur ASP.NET Core MVC (V 5.0). Nous utiliserons un Data Seed pour générer des fakes data et aussi nous utiliserons Entity Framework Core pour interagir avec la base de données MYSQL. À la fin du tutorial le code sera disponible sur mon repo gitlab : https://gitlab.com/eroamba/crud-dotnet-core-mvc-avec-mysql

Avant de continuer, je vais vous donner une brève introduction à .NET 5.0 MVC.

Attention avant de commencer ne pas confondre ASP.NET MVC et ASP.NET Core MVC il y a une différence et je vous explique

D'abord Asp.net Core est une nouvelle version d'Asp.net publiée par Microsoft. C'est un framework open-source et peut être exécuté sur différent système comme Windows, Mac ou Linux. Il a été lancé à l'origine en tant qu'ASP.NET 5, mais il a ensuite été renommé ASP.NET Core et porte toujours le même nom.

Donc ASP.NET CORE est multi plateforme (Windows, Linux, MAC) par contre ASP.NET était que pour environnement windows.

Pour plus d'information voici un lien complet ici.

ASP.NET Core MVC est un framework qui permet de concevoir des applications web en utilisant le design pattern MVC(Model-View-Controller)

Structure du projet

Pour ce tutoriel on utilisera une table Contact avec les différents champs suivants :

Table Contact

Création du projet

Pour débuter nous allons créer notre Solution (projet) avec Visual studio 2019 pour faire plus simple. Mais je montrerai aussi dans un autre tutoriel comment créer une solution .net core mvc avec Visual Studio Code qui est différent de Visual studio.

Création du projet

Choisissons notre type de projet qui ASP.NET Core (Model-View-Controller)

Type de projet

Suivons les étapes et choisissons un nom pour notre projet CRUD MVC

Configuration du projet

Super notre solution est créée avec les différents éléments du projet.

Projet créé

Installation des packages

Installons les packages nécessaires pour notre projet :

Installation packages

Création du modèle Contact

Créons notre modèle Contact dans le dossier Models :

public int Id { get; set; }
[Required(ErrorMessage = "Le nom est requis.")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Le prénom est requis.")]
public string LastName { get; set; }
[Required(ErrorMessage = "Le numéro de téléphone est requis.")]
public string Phone { get; set; }
public string Email { get; set; }

Modèle Contact

Configuration de la base de données

DataContext

Créons notre DataContext :

public DataContext(DbContextOptions options) : base(options)
{
}
public DbSet<Contact> Contacts { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    builder.Entity<Contact>();
}

Seed Data

Créons aussi notre classe Seed pour générer des données de test :

public static async Task SeedData(DataContext context)
{
    var contacts = new List<Contact>{
        new Contact{
             Id=1,
             FirstName="Roamba",
             LastName="Windlassida Epaphras",
             Phone="+225 0103601135",
             Email="eroamba@yahoo.fr",
        },
        new Contact{
             Id=2,
             FirstName="Djama",
             LastName="Lemec",
             Phone="+225 0779157835",
             Email="djama@gmail.com",
        },
    };
    foreach (var item in contacts )
    {
         context.Contacts.Add(item);
    }
     await context.SaveChangesAsync();
}
}

DataContext et Seed

Configuration de la connexion MySQL

Dans le fichier appsettings.json, ajoutons notre chaîne de connexion :

"ConnectionStrings": {
  "DefaultConnection": "server=localhost; port=3306; database=crud_mvc; user=root;"
},

Dans Startup.cs, configurons Entity Framework avec MySQL :

string mySqlConnectionStr = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<DataContext>(opt => opt.UseMySql(mySqlConnectionStr, ServerVersion.AutoDetect(mySqlConnectionStr)));

Configuration MySQL

Configuration du Program.cs

Modifions Program.cs pour initialiser la base de données :

public static async Task Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    using var scope = host.Services.CreateScope();
    var services = scope.ServiceProvider;
    try
    {
        var context = services.GetRequiredService<DataContext>();
        await context.Database.MigrateAsync();
        await Seed.SeedData(context);
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "An error occurred during migration");
    }
    await host.RunAsync();
}

Configuration Program.cs

Migration de la base de données

Créons notre première migration :

Migration

Table créée

Contrôleur Contact

Créons notre contrôleur avec toutes les opérations CRUD :

Contrôleur

Code contrôleur

public class ContactController : Controller
{
    private readonly ILogger<ContactController> _logger;
    private readonly DataContext _db;
    public ContactController(ILogger<ContactController> logger, DataContext db)
    {
        _db = db;
        _logger = logger;
    }
    public async Task<IActionResult> Index()
    {
        var result = await _db.Contacts.ToListAsync();
        return View(result);
    }

    public IActionResult Create()
    {
        return View();
    }
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(Contact contact)
    {
        if (ModelState.IsValid)
        {
            _db.Contacts.Add(contact);
            await _db.SaveChangesAsync();
            TempData["success"] = "Création effectuée avec succès";
            return RedirectToAction("Index");
        }
        return View(contact);
    }
    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null || id == 0)
        {
            return NotFound();
        }
        var contact = await _db.Contacts.FindAsync(id);
        if (contact == null)
        {
            return NotFound();
        }

        return View(contact);
    }
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(Contact contact, int id)
    {
        if (id != contact.Id)
        {
            return NotFound();
        }
        if (ModelState.IsValid)
        {
            _db.Contacts.Update(contact);
            await _db.SaveChangesAsync();
            TempData["success"] = "Mise à jour effectuée avec succès";
            return RedirectToAction("Index");
        }
        return View(contact);
    }
    public async Task<IActionResult> Delete(int? id)
    {
        if (id == null || id == 0)
        {
            return NotFound();
        }
        var contact = await _db.Contacts.FindAsync(id);
        if (contact == null)
        {
            return NotFound();
        }
        _db.Contacts.Remove(contact);
        await _db.SaveChangesAsync();
        TempData["success"] = "Suppresson effectuée avec succès";
        return RedirectToAction("Index");

    }
}

Vues Razor

Layout principal

Modifions le layout principal pour inclure Bootstrap Icons :

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css">
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - CRUD_MVC</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css">
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Contact" asp-action="Index">Contact 2.0</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>
    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2022 - eroambahub.com - <a asp-area="" asp-controller="Contact" asp-action="Index">CRUD ASP.NET Core</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Configuration de la route par défaut

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Contact}/{action=Index}/{id?}");
});

Configuration route

Vue Create.cshtml

@model Contact;
@{
    ViewData["Title"] = "Create";
}
<form method="post">
    <div class="form-group">
        <label asp-for="FirstName">Nom</label>
        <input asp-for="FirstName" type="text" class="form-control" placeholder="Veuillez saisir votre nom">
        <span asp-validation-for="FirstName" class="form-text text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="LastName">Prénoms</label>
        <input asp-for="LastName" type="text" class="form-control" placeholder="Veuillez saisir votre nom">
        <span asp-validation-for="LastName" class="form-text text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Phone">Téléphone</label>
        <input asp-for="Phone" type="text" class="form-control" placeholder="Veuillez saisir votre numéro de téléphone">
        <span asp-validation-for="Phone" class="form-text text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Email">Email address</label>
        <input asp-for="Email" type="email" class="form-control" placeholder="Veuillez saisir votre numéro Email">
        <span asp-validation-for="Email" class="form-text text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary" value="Create">Ajouter</button>
</form>
@section Scripts{
    @{
        <partial name="_ValidationScriptsPartial" />
    }
}

Vue Create

Vue Edit.cshtml

@model Contact;
@{
    ViewData["Title"] = "Edit";
}
<form method="post">
    <div class="form-group">
        <label asp-for="FirstName">Nom</label>
        <input asp-for="FirstName" type="text" class="form-control" placeholder="Veuillez saisir votre nom">
        <span asp-validation-for="FirstName" class="form-text text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="LastName">Prénoms</label>
        <input asp-for="LastName" type="text" class="form-control" placeholder="Veuillez saisir votre nom">
        <span asp-validation-for="LastName" class="form-text text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Phone">Téléphone</label>
        <input asp-for="Phone" type="text" class="form-control" placeholder="Veuillez saisir votre numéro de téléphone">
        <small asp-validation-for="Phone" class="form-text text-danger"></small>
    </div>
    <div class="form-group">
        <label asp-for="Email">Email address</label>
        <input asp-for="Email" type="email" class="form-control" placeholder="Veuillez saisir votre numéro de téléphone">
        <small asp-validation-for="Email" class="form-text text-danger"></small>
    </div>
    <button type="submit" class="btn btn-primary" value="Create">Modifier</button>
</form>
@section Scripts{
    @{
        <partial name="_ValidationScriptsPartial" />
    }
}

![Vue Edit](https://lh5.googleusercontent.com/Kj3v1it7CdmbCcAzis530CYj7xX54RQJQ5uSKMLd7VuntndX2vkddeANVH5U_O1idM7HL-Gkbp6WVqHa7e39apLJHe3BWPZlY9BNFyTDL9OJ30n9kT7Z408cLf4EkeZEWUV5f8xc0BdrdcaOcHwhdIhvNb

Test de l'application

Application fonctionnelle

Interface de l'application

Déploiement en production

Configuration pour la production

Pour le déploiement en production, nous devons modifier certaines configurations :

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();
    app.UseRouting();
    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
    });
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Contact}/{action=Index}/{id?}");
    });
}

Configuration appsettings.json pour production

"ConnectionStrings": {
  "DefaultConnection": "server=***host***; port=3306; database=*******; user=******; password=*******;"
},
"Kestrel": {
  "Endpoints": {
    "Http": {
      "Url": "http://testlinode.com:5002"
    },
    "Https": {
      "Url": "https://testlinode.com:5000"
    }
  }
},

Configuration Nginx

server {
listen 80;
server_name testlinode.com;
return 301 https://$host$request_uri;
}
server {
listen 443;
server_name testlinode.com;
ssl_certificate /etc/letsencrypt/live/www.testlinode.com/fullchain.pem;
ssl_certificate_key       /etc/letsencrypt/live/www.testlinode.com/privkey.pem;
ssl on;
ssl_session_cache  builtin:1000 shared:SSL:10m;
ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
ssl_prefer_server_ciphers on;
gzip on;
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_proxied any;
gzip_buffers 16 8k;
gzip_disable "MSIE [1-6].(?!.*SV1)";
location / {
proxy_pass            https://178.79.168.173:5000;
proxy_http_version 1.1;
proxy_set_header   Upgrade $http_upgrade;
proxy_set_header   Connection keep-alive;
proxy_set_header   Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header   X-Forwarded-Proto $scheme;
}
}

Application en production

Service systemd

Configuration du service pour maintenir l'application en vie :

[Unit]
Description=Example .NET Web API App running on Ubuntu
[Service]
WorkingDirectory=/home/epa/crudmvc
ExecStart=/usr/bin/dotnet /home/epa/crudmvc/'CRUD MVC.dll'
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
SyslogIdentifier=CRUDMVC
User=root
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target

Service en production

Application déployée

Test en production

Testons notre application déployée sur https://testlinode.com

Pour plus de détails sur le déploiement, consultez l'article : Comment déployer .net 5 api sur linux

Conclusion

Nous avons créé une application CRUD complète avec ASP.NET Core MVC et MySQL incluant :

Modèle Contact avec validations ✅ Entity Framework Core pour l'accès aux données ✅ CRUD complet (Create, Read, Update, Delete) ✅ Interface Bootstrap responsive ✅ Data Seed pour les données de test ✅ Migration automatique de la base de données ✅ Déploiement en production avec Nginx et SSL

Cette application constitue une base solide pour des projets plus complexes utilisant ASP.NET Core MVC.

Le code complet est disponible sur GitLab.


#AspNetCore #MVC #CRUD #MySQL #EntityFramework #DotNet #CSharp #VisualStudio