En este post se explica cómo cargar archivos utilizando ASP.NET Core con un sencillo ejemplo. Con ese fin, se implementará un CRUD con el que crear, editar y eliminar imágenes de un carrusel dinámico.

Se estará trabajando con la tecnología de Entity Framework con la que haremos la base de datos mediante el modelo Code First. Para ello instalamos los siguientes tres paquetes:

  • Microsoft.EntityFrameworkCore.SqlServer (Versión 5.0.14)
  • Microsoft.EntityFrameworkCore.Tools (Versión 5.0.14)
  • Microsoft.aspnetcore.mvc.core (La versión más reciente)

Una vez instalados los paquetes, vamos a crear el modelo de datos para cargar las imágenes.

Creación de la clase modelo

En la carpeta Models, en un archivo C# con el nombre de Carousel,  definimos las propiedades con las que seguardará la información de los archivos.

En este caso tenemos un Id que es la clave primaria, un nombre, una descripción y el título de la imagen.

namespace CarouselImagesApp.Models
{
    public class Carousel
    {
        [Key]
        public int CarouselId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        [DisplayName("Image Name")]
        public string ImageName { get; set; }
    }
}

En la carpeta Models también vamos a crear el contexto (ApplicationDbContext), que hereda de DbContext y que traemos de Microsoft.EntityFrameworkCore. En el constructor, le pasamos las opciones del contexto y lo recogemos en la base.

Para crear la tabla, hay que declarar una propiedad correspondiente al modelo Carousel.

namespace CarouselImagesApp.Models
{
    public class ApplicationDbContext: DbContext
    {
           public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
            { }
           public DbSet<Carousel> Carousel { get; set; } 
    }
}

Configuración de los archivos appsettings.json y Startup.cs

Antes de realizar la migración, es necesario especificar la información sobre qué tipo de base de datos vamos a usar y la cadena de conexión correspondiente. En esta aplicación trabajaremos con SQL Server en local.

En appsettings.json añadimos la siguiente líneas:

"AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=CarouselImagesAppBBDD;Trusted_Connection=True;"
  }

En el archivo Startup.cs añadimos lo siguiente en ConfigureServices:

services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

En Herramientas > Administración de paquetes NuGet > Consola del Administrador de paquetes, hacemos la migración y actualizamos la base de datos con los siguientes comandos:

  • PM> Add-Migration «FirstMigration»
  • PM> Update-Database

Creación del controlador

Para agregar el controlador (CarouselSlidersController), lo haremos seleccionando la opción de Controlador de MVC con vistas que usan Entity Framework.

Aparecerá una ventana que rellenaremos como se ve a continuación:

VentanaControlador

Esto nos generará el controlador con todas las operaciones requeridas para el modelo y las vistas correspondientes a cada una de ellas.

En la carpeta Views > Shared >_Layout.cshtml creamos un enlace para poder acceder al Index de CarouselSliders desde el menú principal.

<li class="nav-item">
      <a class="nav-link text-dark" aspare="" asp-controller="CarouselSliders" 
      asp-action="Index">Carousel sliders</a>
</li>

Además, en el archivo Startup.cs modificamos la ruta de inicio de la aplicación.

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

Carga de archivos (Create)

Ahora mismo en la vista Create tenemos como nombrar la imagen pero no como cargarla, por lo que debemos crear un control que sirva para cargar el archivo.

Para eso dentro del modelo de Carousel vamos a agregar una nueva propiedad de tipo IFormFile, con el fin de mostrar el control de cargar imágenes. El atributo NotMapped se usa para especificar que una propiedad no debe asignarse a una tabla o columna en la base de datos.

        [NotMapped]
        [DisplayName("Upload File")]
        public IFormFile ImageFile { get; set; }

En la vista Create ahora sustituiremos el control que corresponde a ImageName por el siguiente:

<div class="form-group">
       <label asp-for="ImageFile" class="control-label"></label>
       <input asp-for="ImageFile" accept="image/*" />
       <span asp-validation-for="ImageFile" class="text-danger"></span>
</div>

Con accept=»image/*» en la etiqueta input aceptaremos todo tipo de archivo. Cuando un formulario contiene una carga de imágenes es importante indicar el atributo enctype=»multipart/form-data».

<form asp-action="Create" enctype="multipart/form-data">

Ahora nuestra vista se vería así:

VistaCreate

A continuación nos dirigimos al controlador al método post de Create. Entonces, en vez de enlazar ImageName, ponemos ImageFile.

public async Task<IActionResult> Create([Bind("CarouselId,Name,Description,ImageFile")] Carousel carousel)

Antes de guardas las imágenes, primero tenemos que cargarlas en la carpeta wwwroot y luego hacer el registro en la base de datos.

Para ello comprobamos si el modelo que le llega al método es válido o no. Sí lo es, guardaremos la imagen en una carpeta llamada image y agregada dentro de wwwroot. Por eso creamos el método privado UploadedFile.

Pero antes, inyectamos en el constructor la siguiente propiedad IWebHostEnvironment para obtener la ruta de la carpeta.

        private readonly ApplicationDbContext _context;
        private readonly IWebHostEnvironment _hostEnvironment;
        public CarouselSlidersController(ApplicationDbContext context,                  IWebHostEnvironment hostEnvironment)
        {   
            _context = context;
            _hostEnvironment = hostEnvironment;
        }

En los siguientes párrafos se explica en detalle el método privado UploadedFile y finalmente se muestra el código completo.

Recuperamos el nombre de la imagen cargada:

fileName=Path.GetFileNameWithoutExtension(carouselSlider.ImageFile.FileName);

Para evitar la duplicidad de imágenes y que cada una de ellas tenga un nombre único, implementamos este código donde se obtiene la extensión y se concatena con la hora y la fecha del servidor actual, ese nombre será el que le pasaremos al modelo como ImageName:

string extension = Path.GetExtension(carouselSlider.ImageFile.FileName);
carouselSlider.ImageName = fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;

Ahora almacenamos la ruta:

string path = Path.Combine(wwwRootPath + "/image/", fileName);

Y la cargamos con el siguiente código:

using (var fileStream = new FileStream(path, FileMode.Create))
                {
                    carouselSlider.ImageFile.CopyTo(fileStream);
                }

El metódo delvolveria el FileName y finalmente quedaría así:

private string UploadedFile(Carousel carouselSlider)
        {
            string fileName = null;
            if (carouselSlider.ImageFile != null)
            {
                string wwwRootPath = _hostEnvironment.WebRootPath;
                fileName = Path.GetFileNameWithoutExtension(carouselSlider.ImageFile.FileName);
                string extension = Path.GetExtension(carouselSlider.ImageFile.FileName);
                carouselSlider.ImageName = fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;
                string path = Path.Combine(wwwRootPath + "/image/", fileName);
                using (var fileStream = new FileStream(path, FileMode.Create))
                {
                    carouselSlider.ImageFile.CopyTo(fileStream);
                }
            }
            return fileName;
        }

En el método Create llamaríamos a UploadedFile y luego guardaríamos la imagen en la base de datos:

public async Task<IActionResult> Create([Bind("CarouselId,Name,Description,ImageName")] Carousel carousel)
        {
            if (ModelState.IsValid)
            {
                //Guardar imágenes wwwroot/image
                carousel.ImageName = UploadedFile(carousel);

                //Guardar en la BBDD
                _context.Add(carousel);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            return View(carousel);
        }

En el Index, añadimos el siguiente código de un carrusel de Bootstrap. Para poder desplegar las imágenes, utilizamos el helper @Url.Content().

@{ 
    int a = 0;
} 
<div id="carouselExampleIndicators" class="carousel slide" data-ride="carousel">
    <ol class="carousel-indicators">
        <li data-target="#carouselExampleIndicators" data-slide-to="0" class="active"></li>
        <li data-target="#carouselExampleIndicators" data-slide-to="1"></li>
    </ol>
    <div class="carousel-inner">
        @foreach (var i in Model)
        {
            a++;
            var active = a == 1 ? "active" : "";
            <div class="carousel-item @active">
                <img class="d-block w-100" src="@Url.Content("~/image/"+i.ImageName)" alt="@i.Name">
                <div class="carousel-caption d-none d-md-block">
                    <h5>@i.Name</h5>
                    <p>@i.Description</p>
                </div>
            </div>
        }
    </div>
    <a class="carousel-control-prev" href="#carouselExampleIndicators" role="button" data-slide="prev">
        <span class="carousel-control-prev-icon" aria-hidden="true"></span>
        <span class="sr-only">Previous</span>
    </a>
    <a class="carousel-control-next" href="#carouselExampleIndicators" role="button" data-slide="next">
        <span class="carousel-control-next-icon" aria-hidden="true"></span>
        <span class="sr-only">Next</span>
    </a>
</div>

Carga de archivos (Edit)

Para la vista Edit, ocultamos el input ImageName para que le siga llegando al método el nombre de la imagen existente y luego añadimos el control que carga el archivo de la misma forma que lo hicimos en la vista Create.

Es importante no olvidar poner en el formulario enctype=»multipart/form-data», de lo contrario no funcionará.

<div class="form-group">
          <input asp-for="ImageName" readonly="readonly" type="hidden" />
</div>  
      <div class="form-group">
         <label asp-for="ImageFile" class="control-label"></label>
         <input asp-for="ImageFile" accept="image/*" />
         <span asp-validation-for="ImageFile" class="text-danger"></span>
     </div>

En el controlador, en el método post lo que se hace es comprobar que tanto el nombre como el archivo no son nulos, entonces se borra de la carpeta image el antiguo archivo y se carga el nuevo. Finalmente actualizamos la base de datos.

if (ModelState.IsValid)
            {
                try
                {
                    if (carousel.ImageFile != null)
                    {
                        if (carousel.ImageName != null)
                        {
                            string filePath = Path.Combine(_hostEnvironment.WebRootPath, "image", carousel.ImageName);
                            System.IO.File.Delete(filePath);
                        }
                        carousel.ImageName = UploadedFile(carousel);
                    }
                    _context.Update(carousel);
                    await _context.SaveChangesAsync();
                }

Eliminación de archivos (Delete)

En la vista Delete, no modificamos nada pero en el controlador hacemos algo parecido a lo que hicimos en el Edit. Guardamos la ruta de la imagen, comprobamos que existe dentro de la carpeta image y finalmente eliminamos, quedaría así:

public async Task<IActionResult> DeleteConfirmed(int id)
        {
            var carousel = await _context.Carousel.FindAsync(id);

            //Borramos de wwwroot/image
            var imagePath = Path.Combine(_hostEnvironment.WebRootPath, "image", carousel.ImageName);
            if (System.IO.File.Exists(imagePath))
                System.IO.File.Delete(imagePath);

            //Borramos
            _context.Carousel.Remove(carousel);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }

Siguiendo estos pasos se obtiene una gestión completa de un CRUD de imágenes, el cual se puede adaptar a otro tipos de archivos con los que se quiera trabajar, ya que es un código fácilmente adaptable.

Autor/a: Nicole Martínez de Jesús
Curso: Desarrollo Web Full Stack, MultiCloud y Multiplataforma 
Centro: Tajamar 
Año académico: 2021-2022 
Código: Enlace a GitHub
LinkedIn: Mi perfil

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.