Formularios web AJAX con jQuery en ASP.NET Core MVC

El nuevo framework de Microsoft ASP.NET Core MVC, no implementa por defecto mecanismos para utilizar AJAX de una manera transparente para el desarrollador como hacían las anteriores versiones de ASP.NET, a través del uso de Ajax Helpers como @Ajax.BeginForm() (ASP.NET MVC 5 y full Framework >= 4.5) o los ya primitivos <asp:UpdatePanel /> en WebForms.

Esto quiere decir que en ASP.NET Core MVC, debemos volver al JavaScript para implementar AJAX en nuestras aplicaciones web, y la manera mas óptima y recomendada por Microsoft es utilizar jQuery.

En este artículo veremos cómo implementar un formulario web Http POST en ASP.NET Core MVC, que utilice AJAX para enviar datos al servidor, y a su vez, reciba de manera asíncrona una respuesta HTML que le permita actualizar información visual en la misma página.

Creando el proyecto

Para este ejemplo, utilizaremos Visual Studio 2017 para crear un nuevo proyecto Web MVC, con plataforma de destino .NET Core y el framework ASP.NET Core 2.2.

La aplicación de ejemplo consistirá en un sencillo sistema de Blog, en el cual, a través de un formulario, introduciremos Posts que serán enviados al Servidor para ser almacenados en una Base de Datos. Además, cada vez que creemos un nuevo Post, se actualizará una lista de Posts en pantalla con el nuevo contenido de la Base de Datos. 

Nota: Todo este proceso lo haremos sin enviar en ningún momento la página completa al Servidor, o sea, mediante AJAX enviaremos solo los datos y recibiremos solo la información necesaria para actualizar parcialmente la página.

form ajax mvc core

El Modelo de datos

En primer lugar crearemos la clase Post.cs que será el Modelo de datos principal de la aplicación.

    public class Post
    {
        public Post()
        {
            this.Id = Guid.NewGuid();
            this.Fecha = DateTime.Now;
        }

        [Required]
        [Key]
        public Guid Id { get; set; }

        [Required]
        public DateTime Fecha { get; set; }

        [StringLength(50)]
        [Required(ErrorMessage ="Este campo es obligatorio.")]
        [RegularExpression(@"^[A-Z a-z0-9ÑñáéíóúÁÉÍÓÚ\\-\\_]+$",
            ErrorMessage = "El Nombre debe ser alfanumérico.")]
        [Display(Name = "Nombre")]
        public string Nombre { get; set; }

        [RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
            ErrorMessage = "Dirección de Correo electrónico incorrecta.")]
        [StringLength(50)]
        [Display(Name = "Correo electrónico")]
        public string Email { get; set; }

        [StringLength(100)]
        [Required(ErrorMessage = "Este campo es obligatorio.")]
        [Display(Name = "Título")]
        public string Titulo { get; set; }

        [StringLength(1500)]
        [Required(ErrorMessage = "Este campo es obligatorio.")]
        [DataType(DataType.MultilineText)]
        [Display(Name = "Comentario")]
        public string Comentario { get; set; }
    }

También crearemos la clase auxiliar PostViewModel.cs, que contendrá los datos del Modelo que posteriormente serán enviados a la Vista principal de la aplicación.

    public class PostViewModel
    {
        public Post Post { get; set; }
        public List<Post> PostList { get; set; }
    }

El Controlador

A continuación crearemos el Controlador BlogController.cs. En este incluiremos dos Acciones básicas, que gestionarán las peticiones GET y POST enviadas desde el explorador.

    public class BlogController : Controller
    {
        private readonly ApplicationDbContext _dbContext;

        public BlogController(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        // GET: Blog/Post
        [HttpGet]
        public IActionResult Post()
        {
            var _model = new PostViewModel()
            {
                Post = new Post(),
                PostList = _dbContext.Posts.OrderByDescending(x => x.Fecha).ToList()
            };
            return View(_model);
        }

        // POST: Blog/Post
        [HttpPost]
        public IActionResult Post(Post post)
        {
            if (ModelState.IsValid)
            {
                _dbContext.Add(post);
                _dbContext.SaveChanges();
                return PartialView("_PostListPartial", 
                    _dbContext.Posts.OrderByDescending(x => x.Fecha).ToList());
            }
            else
            {
                return null;
            }
        }
    }

Nota: Como podemos ver, el Controlador recibe en su constructor un objeto del tipo ApplicationDbContext (Contexto de datos de Entity Framework Core), mediante Inyección de Dependencias. Para más información acerca de cómo implementar un DbContext en Entity Framework Core pueden consultar el siguiente Post: Entity Framework Core y SqLite in-memory en ASP.NET Core.

La Acción GET

public IActionResult Post(): Aquí crearemos un nuevo objeto del tipo PostViewModel, cuya propiedad PostList contendrá una lista ordenada de todos los Posts existentes en la Base de Datos. La Acción devolverá la Vista asociada (Post.cshtml) con el Modelo de datos anteriormente creado.

La Acción POST

public IActionResult Post(Post post): Aquí recibiremos como parámetro un objeto del tipo Post con los datos enviados desde el formulario. Si los datos recibidos son válidos, guardaremos el Post en la Base de Datos, y devolveremos una Vista parcial (_PostListPartial.cshtml) con una lista ordenada de todos los Posts existentes en la Base de Datos. En caso caso contrario no devolveremos nada (Null).

La Vista

Antes de implementar la Vista principal de la aplicación, crearemos la Vista parcial _PostListPartial.cshtml. Está se encargará de construir una lista con los Posts existentes en la Base de Datos.

@model  List<Post>

@foreach (var post in Model)
{
    <div class="row">
        <div class="col-md-12">
            <div>
                <strong>@post.Fecha.ToShortDateString() @post.Fecha.ToLongTimeString()</strong>&nbsp;
                <span>@post.Nombre</span>&nbsp;
                <span>@post.Email</span>
            </div>
            <strong>@post.Titulo</strong>
            <p>
                @post.Comentario
            </p>
            <hr />
        </div>
    </div>
}

Por último solo nos quedaría crear la Vista principal Post.cshtml. Esta es realmente la parte más importante de nuestra aplicación de ejemplo, ya que es aquí donde implementaremos el código jQuery (AJAX) encargado de establecer y gestionar el flujo de datos entre el explorador del usuario y el Controlador de la aplicación.

Implementando AJAX - el Script jQuery 

Antes de ver cómo crear la Vista principal, analizaremos brevemente el funcionamiento del Script jQuery que nos permitirá habilitar AJAX en nuestro formulario web. 

Una plantilla estándar de este Script jQuery sería la siguiente:

     <script type="text/javascript">

        $(function () {
            $("#AjaxForm").submit(function (e) {
                e.preventDefault();

                $.ajax({
                    url: "@Url.Action("Action", "Controller")", // Url
                    data: {
                        param1: "value1", // Parámetros
                        param2: "value2",
                        // ...
                    },
                    type: "post"  // Verbo HTTP
                })
                // Se ejecuta si todo fue bien.
                .done(function (result) {
                    if (result != null) {
                    }
                    else {
                    }
                })
                // Se ejecuta si se produjo un error.
                .fail(function (xhr, status, error) {

                })
                // Hacer algo siempre, haya sido exitosa o no.
                .always(function () {

                });
            });
        });

    </script>

Como vemos, la función del método principal $("#AjaxForm").submit(function (e) {...}) se ejecutará cuando se produzca un Submit en el formulario cuyo "id=" sea "AjaxForm". A continuación, mediante e.preventDefault() cancelaremos el Submit realizado, y pasaremos el control al método $.ajax({...}) que se encargará de enviar los datos del formulario a la Acción del Controlador correspondiente, y esperar de manera asíncrona una respuesta.

La Vista principal

Llegados a este punto, ya podemos crear la Vista Post.cshtml. El código sería el siguiente:

@model PostViewModel

<h3>BlogPost</h3>
<form id="AjaxForm">
    <div class="row">
        <div class="col-md-6">
            <div class="form-group">
                <input asp-for="Post.Nombre" id="Nombre" class="form-control input-sm" placeholder="Nombre" />
                <span asp-validation-for="Post.Nombre" class="text-danger"></span>
            </div>
        </div>

        <div class="col-md-6">
            <div class="form-group">
                <input asp-for="Post.Email" id="Email" class="form-control input-sm" placeholder="Email" />
                <span asp-validation-for="Post.Email" class="text-danger"></span>
            </div>
        </div>

        <div class="col-md-12">
            <div class="form-group">
                <input asp-for="Post.Titulo" id="Titulo" class="form-control input-sm" placeholder="Título" />
                <span asp-validation-for="Post.Titulo" class="text-danger"></span>
            </div>
        </div>

        <div class="col-md-12">
            <div class="form-group">
                <textarea asp-for="Post.Comentario" id="Comentario" class="form-control" placeholder="Comentario"></textarea>
                <span asp-validation-for="Post.Comentario" class="text-danger"></span>
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col-md-9">
            <div id="ErrorAlert" class="alert alert-danger" style="display:none" role="alert">
                Error en los datos enviados!
            </div>
            <div id="ExitoAlert" class="alert alert-success" style="display:none" role="alert">
                Datos recibidos correctamente!
            </div>
        </div>
        <div class="col-md-3">
            <div class="form-group float-right">
                <img id="AjaxLoader" alt="Enviando ..." style="display:none" src="~/Images/loading.gif" />
                &nbsp;&nbsp;
                <button id="SubmitBtn" type="submit" class="btn btn-primary">
                    Enviar datos
                </button>
            </div>
        </div>
    </div>
</form>

<h3>PostList</h3>
<div id="PostList">
    <partial name="_PostListPartial" model="@Model.PostList" />
</div>

@section scripts {

    <partial name="_ValidationScriptsPartial" />

    <script type="text/javascript">

        $(function () {
            $("#AjaxForm").submit(function (e) {
                e.preventDefault();

                // Mostramos el Ajax Loader
                $("#AjaxLoader").show("fast");

                // Deshabilitamos el botón de Submit
                $("#SubmitBtn").prop("disabled", true);

                $.ajax({
                    url: "@Url.Action("Post", "Blog")", // Url
                    data: {
                            // Datos / Parámetros
                            Comentario: $("#Comentario").val(),
                            Nombre: $("#Nombre").val(),
                            Email: $("#Email").val(),
                            Titulo: $("#Titulo").val()
                    },
                    type: "post"  // Verbo HTTP
                })
                // Se ejecuta si todo fue bien.
                .done(function (result) {                    
                    if (result != null) {

                        // Actualiza el resultado HTML
                        $("#PostList").html(result);

                        // Un pequeño esfecto especial ;)
                        $("#PostList .row").first().hide();
                        $("#PostList .row").first().slideToggle("fast");

                        // Limpia el formulario
                        $("#Comentario").val("");
                        $("#Nombre").val("");
                        $("#Email").val("");
                        $("#Titulo").val("");

                        // Escondemos el Ajax Loader
                        $("#AjaxLoader").hide("slow");

                        // Habilitamos el botón de Submit
                        $("#SubmitBtn").prop("disabled", false);

                        // Mostramos un mensaje de éxito.
                        $("#ExitoAlert").show("slow").delay(2000).hide("slow");
                    }
                })
                // Se ejecuta si se produjo un error.
                .fail(function (xhr, status, error) {                    
                    // Mostramos un mensaje de error.
                    $("#ErrorAlert").show("slow").delay(2000).hide("slow");

                    // Escondemos el Ajax Loader
                    $("#AjaxLoader").hide("slow");

                    // Habilitamos el botón de Submit
                    $("#SubmitBtn").prop("disabled", false);
                })
                // Hacer algo siempre, haya sido exitosa o no.
                .always(function () {
                    
                });
            });
        });
    </script>

} 

La Librería jQuery

Como podemos ver, en la Vista Post.cshtml, no hemos hecho referencia en ningún momento a la librería JavaScript jQuery. Esto se debe a que Visual Studio la incluye en la página maestra o Layout (_Layout.cshtml) cuando creamos un nuevo proyecto web ASP.NEt Core MVC.

Nota: También incluye por defecto en la página maestra (Layout) las librerías Bootstrap, hojas de estilo y JavaScripts de la aplicación.

El "Binding" de datos

Los datos del formulario que son enviados al Servidor, los definimos en la propiedad data: {...} dentro del método $.ajax({...}) del script jQuery.

data: {
         // Datos / Parámetros
         Comentario: $("#Comentario").val(),
         Nombre: $("#Nombre").val(),
         Email: $("#Email").val(),
         Titulo: $("#Titulo").val()
}

Como podemos ver, los identificadores "id=" de las etiquetas (<input />) encargadas de capturar los datos del formulario, coinciden exactamente con las propiedades del Modelo de datos de la aplicación.

Esto es absolutamente necesario para que la Acción IActionResult Post(Post post) del Controlador, pueda reconocer mediante el mecanismo de Binding de ASP.NET Core MVC, que los datos recibidos hacen referencia al objeto complejo del Modelo de datos Post.cs

La validación de datos del formulario

Un punto importante en cualquier formulario web, es la validación de los datos que enviamos al Servidor. Cuando creamos un proyecto web ASP.NET Core MVC con Visual Studio, este crea una vista parcial _ValidationScriptsPartial.cshtml que contiene las referencias a los scripts jQuery de validación jquery.validate.*.

Para utilizar esta validación en nuestra Vista principal Post.cshtml, bastaría con incluir esta Vista parcial en la sección @section scripts {...} de nuestra página.

@section scripts {

    <partial name="_ValidationScriptsPartial" />
    
    // ...
}

Por supuesto, es necesario también incluir para cada campo del formulario a validar, su etiqueta correspondiente con el TagHelper asp-validation-for="...".

<span asp-validation-for="Post.Comentario" class="text-danger"></span>

Nota: Para más información acerca de la validación de formularios en ASP.NET MVC, pueden consultar el Post Validación de formularios en ASP.NET MVC - Unobtrusive Validate.

 

   Etiquetas.NET MVC ASP.NET Core jQuery AJAX

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

  Comentarios

Edgardo Edgardo

Me funciono muy bien. Buscando y buscando ejemplos llegue a este, me dejo muy claro todos los conceptos. Muchas gracias!
Joaqu&#237;n Joaquín

Muchas gracias excelente aporte, me funcionó perfectamente ! :)
Anonimo Anonimo

Tengo una consulta, quiero hacer que el formulario pueda borrar los registros. Como podría implementarlo?
Persa Persa

Muchas Gracias Rafael corregí mi error y ya no me marca error en OrderByDescending el problema ahora que tengo es que al momento de llenar el formulario y le doy clic en el formulario en el botón Enviar datos, en el Controlador BlogController.cs me sale un error específicamente en la peticiones POST en la linea donde va: _dbContext.Add(post); me sale un mensaje que dice "'The entity type 'PostViewModel' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.' espero me puedas ayudar saludos
"
Persa Persa

Me marca error en OrderByDescending ¿alguien sabe porque ?
Rafael Acosta Administrador Rafael Acosta

@Persa:

OrderByDescending es un método de extensión de la librería System.Linq.  Comprueba que en tu código estés utilizando using System.Linq;


David Zambrano David Zambrano

Felicitaciones Rafael, la manera de explicar a manera detallada y concisa, gracias por tu tiempo invertido y sobre todo por compartir este conocimiento, seguire tu blog y si tienes pagina de FB o YouTube seguro ahí estaré.

Saludos
Rafael Acosta Administrador Rafael Acosta

@julio Chile:

Muchas gracias por tu comentario.


Recuerda que compartir los artículos del Blog en redes sociales y hacer click en los banners de publicidad de vez en cuando, es fundamental para que este Blog siga ofreciendo publicaciones de calidad y en español.


julio Chile julio Chile

Gracias después de dar jugo todo el día buscando porfin pille algo que sirve muchas gracias
David Valdez David Valdez

Gracias, muy bueno...
Rafael Acosta Administrador Rafael Acosta

@Jorge Abel Burgos:

Muchas gracias por tu comentario, espero que sigas visitando este Blog y compartiendo los artículos en las redes sociales y foros de programación.


Jorge Abel Burgos Jorge Abel Burgos

WAOOOO... Muchas gracias amigo por este maravilloso tutorial, es de lo mejor que he visto en la web, muy claro, consiso y detallado. Muchas felicidades, espero que continues asi.
Rafael Acosta Administrador Rafael Acosta

@Wildo Ruiz Crespi:

Gracias por tu comentario. Compartir los artículos en la redes sociales es fundamental para que este Blog siga ofreciendo publicaciones de calidad y en español.


Wildo Ruiz Crespi Wildo Ruiz Crespi

Muy bueno el ejemplo, el código es bastante limpio y ordenado.

Gracias.


Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto