Afbeeldingen uploaden in .NET Core MVC

Inleiding

Deze post gaat over het uploaden van afbeeldingen in .NET MVC Core. In veel applicaties is een afbeelding gekoppeld aan een class. Je kan daarbij denken aan een profielfoto voor personen, of aan afbeeldingen van producten. Hieronder laat ik stap-voor-stap zien wat je moet coderen om een afbeelding te koppelen aan een entiteit.
Ik ga er wel van uit dat je weet hoe je in Visual Studio een .NET Core MVC Web Applicatie moet maken.

Maak een nieuwe map

We maken een nieuwe map img in wwwroot om de afbeeldingen in op te slaan.

Nieuwe map img in wwwroot

Voeg een class toe

In dit voorbeeld beheren we productgegevens. Van een product willen we de volgende gegevens opslaan:

  • Een primaire sleutel
  • De naam het het product
  • De omschrijving van het product
  • De afbeelding van het product

Om dit te coderen, voegen we een class Product.cs toe aan de map Models.

class product.cs in Models

Gebruik de volgende code voor jouw class:
(je mag het commentaar weglaten, als je wilt)

using System.ComponentModel.DataAnnotations;

namespace AfbeeldingUploaden.Models
{
    public class Product
    {
        /// <summary>
        /// De primaire sleutel
        /// </summary>
        [Key]
        public int Id { get; set; }

        /// <summary>
        /// De naam van het Product.
        /// <para>De naam is verplicht, en mag maximaal 80 tekens groot zijn</para>
        /// </summary>
        [Required, StringLength(80)]
        public string Naam { get; set; }

        /// <summary>
        /// De omschrijving van het product.
        /// <papar>De omschrijving mag maximaal 255 tekens groot zijn.</papar>
        /// </summary>
        public string Omschrijving { get; set; }

        /// <summary>
        /// De naam van de afbeelding.
        /// <para>We slaan de afbeelding op in de map 'img'. 
        /// Met deze naam kunnen we de afbeeling terugvinden.</para>
        /// <para>De afbeeldingsnaam mag maximaal 255 tekens groot zijn</para>
        /// </summary>
        [StringLength(255)]
        public string Afbeelding { get; set; }
    }
}

 

Werk met een database

Als je al weet hoe je een dbcontext met connectiestring moet maken en gebruiken, lees dan verder over Controllers en Views.

Om onze producten op te slaan in een database moeten we libraries toevoegen aan onze solution. Dit project gebruikt SQL Server, dus we gaan daarvoor een pakket installeren via de Package Manager Console.

Toets het volgende commando om het pakket te installeren:

Install-Package Microsoft.EntityFrameworkCore.SqlServer

Definieer een databasecontext

We gebruiken een DbContext om de entiteiten vast te leggen die we gaan opslaan en beheren. DbContext is onderdeel van Entity Framework en zorgt er voor dat we CRUD operaties kunnen uitvoeren met onze gegevens. Maak in het project AfbeeldingUploaden een nieuwe map Data, en plaats daarin de class AfbeeldingUploadenDbContext.cs met de volgende code:

using AfbeeldingUploaden.Models;
using Microsoft.EntityFrameworkCore;

namespace AfbeeldingUploaden.Data
{
    public class AfbeeldingUploadenDbContext: DbContext
    {
        /// <summary>
        /// De constructor van de database context
        /// </summary>
        /// <param name="options">De optie parameters voor deze dbcontext,
        /// bijvoorbeeld de connectionstring</param>
        public AfbeeldingUploadenDbContext(
            DbContextOptions<AfbeeldingUploadenDbContext> options)
            : base(options)
        {
        }

        /// <summary>
        /// De DbSet Products komt overeen met de tabel Products in de database
        /// </summary>
        public DbSet<Product> Products { get; set; }
    }
}

 

Bepaal de connectionstring

De naam van de database server, de login gegevens en de naam van de database zijn samengevoegd in de connectionstring in appsettings.json. In appsettings.json worden alle settings en opties van de applicatie opgeslagen. In dit voorbeeld gebruiken de een lokale database (localdb). We verbinden de applicatie direct met een sql-server bestand zonder database server. Voeg de volgende code toe aan appsettings.json om gebruik te kunnen maken van localdb:

"ConnectionStrings": {
    "AfbeeldingUploadenDbContext": "Server=(localdb)\\mssqllocaldb;Database=AfbeeldingUploaden;Trusted_Connection=True;MultipleActiveResultSets=true"
  }

Registreer de context en de connectie

Voeg de context met de connectionstring toe aan de services. Wijzig daarvoor de method ConfigureServices als volgt:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddDbContext<AfbeeldingUploadenDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("AfbeeldingUploadenDbContext")));
}

Maak de database

We hebben nu een connectionstring en hebben code gemaakt om de database te maken. Nu gaan we de Package Manager Console gebruiken om de database te maken. Kies in het menu Tools > NuGet Package Manager > Package Manager Console. Typ in de console add-migration Initialiseer. De code om de database te maken wordt nu gegenereerd. Om deze code uit te voeren, typ je update-database in de console. Dit commando voert de code uit die in de migration is gemaakt, en bouwt de database met tabellen.

Uitvoer van add-migration en update-database in de Packet Manager Console

Codeer de Controller en de Views

Genereer Controller en Views

We gebruiken scaffolding om de Controller en de Views te maken. We gaan ze daarna aanpassen om bestanden te kunnen uploaden. Rechts-klik op de map Controllers en kies: Add > New Scaffolded Item…

Kies in het volgende scherm voor MVC Controller with views, using Entity Framework. Klik op Add.

In het dialog kies je dan voor Model class: Product. Kies AfbeeldingUploadenDbContext voor Data context class. Je kan eventueel de gegenereerde controller naam aanpassen van ProductsController naar ProductenController, als je de Nederlandse taal wilt gebruiken in je URLs.

Pas de Views aan

Create View

Open het bestand Views/Producten/Create.cshtml, en voeg aan de form tag het attribuut enctype toe, met de waarde “mulitpart/form-data”. De code voor de div waarin de Afbeelding wordt ingevoerd, wordt vervangen door code om een bestand te uploaden. We gebruiken een input type=”file” in plaats van een standaard input. Let op: de atttibuut name=“AfbeeldingBestand” bepaalt de naam van de parameter in de Create action in de controller.
De code voor de View ziet er dan zo uit:

@model AfbeeldingUploaden.Models.Product

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Product</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create" enctype="multipart/form-data">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Naam" class="control-label"></label>
                <input asp-for="Naam" class="form-control" />
                <span asp-validation-for="Naam" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Omschrijving" class="control-label"></label>
                <input asp-for="Omschrijving" class="form-control" />
                <span asp-validation-for="Omschrijving" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Afbeelding" class="control-label"></label>
                <input type="file" name="afbeeldingBestand" class="form-control" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

De aangepaste Create View ziet er zo uit:

Pas de Controller aan

Create

We passen de HttpPost Action Create aan om de geüploade afbeelding te verwerken. De code van de aangepaste method zier er zo uit:

 public async Task<IActionResult> Create(Product product, IFormFile afbeeldingBestand)
        {

            if (ModelState.IsValid)
            {
                if (afbeeldingBestand != null && afbeeldingBestand.Length > 0)
                {
                    product.Afbeelding = await SaveImage(afbeeldingBestand);
                }

                _context.Add(product);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            return View(product);
        }

Ten eerste, is er een extra parameter IFormFile afbeelingBestand toegevoegd. Let op dat de naam van de parameter dezelfde is als het name attribuut van jouw Create View.

Bij het verwerken van het model controleren we eerst of er een bestand is geüpload en of het bestand niet leeg is. Als dat het geval is, roepen we een helper-method SaveImage aan om het bestand op te slaan. De helper-method geeft de naam van het opgeslagen bestand terug. Die die naam slaan we op in de property Afbeelding van het product.

SaveImage

Laten we kijken naar de code van SaveImage, want hier gebeurt het werkelijke uploadproces.

private async Task<string> SaveImage(IFormFile afbeeldingBestand)
        {
            // vervang spaties met streepjes voor een mooiere url
            string bestandsNaam = Path.GetFileName(afbeeldingBestand.FileName)
                .Replace(' ', '-');
            int nummer = 0;

            string naamZonderEtensie = Path.GetFileNameWithoutExtension(bestandsNaam);
            string extensie = Path.GetExtension(bestandsNaam);

            string opgeslagenNaam = bestandsNaam;
            string afbeeldingPad;
            do
            {
                if (nummer > 0)
                {
                    opgeslagenNaam = $"{naamZonderEtensie}({nummer}){extensie}";
                }
                string imgPad = $"{_environment.WebRootPath}/img";
                afbeeldingPad = System.IO.Path.Combine(imgPad, opgeslagenNaam);
                nummer++;
            } while (System.IO.File.Exists(afbeeldingPad));
            try
            {
                using var stream = new FileStream(afbeeldingPad, FileMode.Create);
                await afbeeldingBestand.CopyToAsync(stream);
                return opgeslagenNaam;
            }
            catch
            {
                return string.Empty;
            }
        }

De method krijgt de parameter IFormFile afbeeldingBestand mee. In de method wordt eerst de bestandsNaam uit bepaald. In dit geval worden ook de spaties vervangen door streepjes. Je krijgt dan een mooiere url voor de afbeelding. Als je dat niet wilt, kan je het stukje .Replace(‘ ‘, ‘-‘) weghalen.

We willen het bestand opslaan in de map wwwroot/img. Om te voorkomen dat we een bestand overschrijven, controleren we of er al een bestand met dezelfde naam bestaat. Als dat het geval is, hernoemen we het bestand met een volgnummer. afbeelding.jpg wordt dan afbeeling(1).jpg. Mocht dat bestand dan ook al bestaan, dan wordt de naam afbeeling(2).jpg, enzovoorts.

Daarna gaan we het bestand opslaan. De naam van het opgeslagen bestand geven we terug. We gebruiken hier een FileStream voor. Als het opslaan van het bestand niet lukt, vangen we de exception af en geven we een lege string terug.

Wat nu te doen?

In dit blogbericht hebben we de basis van het uploaden van een bestand in een ASP.NET Core MVC applicatie geleerd. Om alles echt te laten werken zou je nog het een en ander kunnen toevoegen.

  • Afbeelding uploaden in de Edit View;
  • Controleren of het bestand een afbeelding is
  • De afbeelding laten zien in de views
  • Automatisch een thumbnail afbeelding genereren.
  • Eventueel een afbeelding verwijderen in de Delete action.

Meer hierover op het internet

Ik schrijf mijn blog in het Nederlands, omdat ik heb gemerkt dat studenten op MBO soms de Engelse teksten op het internet niet genoeg begrijpen. De code waarop dit bericht is gebaseerd kan je vinden op Github

De volgende links (in het Engels) zou je kunnen bekijken:

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *

Deze website gebruikt Akismet om spam te verminderen. Bekijk hoe je reactie-gegevens worden verwerkt.