Ajax en ASP.NET Core MVC: Enviar datos complejos al Servidor con jQuery
En este artículo, explicaremos cómo enviar datos complejos desde el cliente al servidor utilizando Ajax en ASP.NET Core. Utilizaremos un ejemplo práctico que incluye Modelos de datos complejos, un Controlador Web API RESTful para manejar la solicitud y una Vista que utiliza Ajax para enviar los datos al servidor.
Configuración del Proyecto
Antes de comenzar, asegúrate de tener configurado un proyecto ASP.NET Core en Visual Studio utilizando la plantilla de aplicación web MVC. En este caso, también vamos a configurar un controlador como una Web API para manejar las solicitudes Ajax desde la vista.
El Proyecto
Para este ejemplo, configuraremos un proyecto ASP.NET Core con una estructura que incluya una Web API para manejar las solicitudes desde el cliente. Aquí están los pasos detallados para la configuración inicial:
-
Crear un Proyecto ASP.NET Core: En Visual Studio, crea un nuevo proyecto utilizando la plantilla "ASP.NET Core Web Application".
-
Seleccionar el Tipo de Proyecto: En el diálogo de configuración, elige "API" como el tipo de proyecto para crear una Web API básica. Esto establecerá la estructura necesaria para desarrollar una API RESTful.
Creación del Controlador Web API
Vamos a crear un controlador PersonController
que maneje las solicitudes Ajax y actúe como una Web API para recibir y procesar datos complejos.
-
Crear el Controlador:
- Haz clic derecho en la carpeta
Controllers
del proyecto. - Selecciona
Agregar
>Nuevo Elemento
. - Selecciona
Controlador API
y nómbraloPersonController
.
- Haz clic derecho en la carpeta
-
Definir el Controlador:
- Agrega el siguiente código al
PersonController
para definir las rutas y la acción para manejar la solicitud POST.
- Agrega el siguiente código al
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class PersonController : ControllerBase
{
[HttpPost]
[Route("SavePerson")]
public IActionResult SavePerson([FromBody] Person person)
{
if (person == null)
{
return BadRequest("Invalid person data.");
}
// Aquí puedes procesar los datos del modelo 'Person'
// Por ejemplo, guardarlos en una base de datos
return Ok(new { success = true, message = "Person data received successfully." });
}
}
En este ejemplo:
[ApiController]
es un atributo que indica que este controlador maneja solicitudes HTTP y proporciona algunas convenciones automáticas de comportamiento que ayudan en la construcción de API.[Route("api/[controller]")]
define la ruta base para las acciones del controlador.api/[controller]
se traduce aapi/person
, porque el nombre del controlador esPersonController
.- La acción
SavePerson
maneja las solicitudes POST en la rutaapi/person/SavePerson
y recibe un objetoPerson
del cuerpo de la solicitud HTTP usando el atributo[FromBody]
.
Creación del Modelo
Definiremos un modelo Person
que contiene información detallada sobre una persona, incluyendo una dirección y una lista de números de teléfono:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
public class PhoneNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
Incluir jQuery desde un CDN
Para utilizar Ajax con jQuery, necesitamos incluir la biblioteca jQuery en nuestro proyecto. Esto se puede hacer añadiendo la siguiente línea en el archivo _Layout.cshtml
ubicado en la carpeta Views/Shared
:
<!DOCTYPE html>
<html>
<head>
<!-- Otros enlaces y estilos -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<!-- Contenido del cuerpo -->
</body>
</html>
Nota: Un CDN (Content Delivery Network) es una red de servidores distribuidos que entrega contenido web a los usuarios según su ubicación geográfica. Usar jQuery desde un CDN tiene varias ventajas:
Velocidad: Mejora el tiempo de carga al entregar archivos desde un servidor cercano al usuario.
Eficiencia: Permite al navegador del usuario almacenar en caché archivos usados frecuentemente, reduciendo el tiempo de carga en futuras visitas.
Escalabilidad: Maneja un alto tráfico de usuarios sin sobrecargar el servidor de origen.
Creación de la Vista
Ahora crearemos una vista SavePerson.cshtml
en la carpeta Views/Person
. Esta vista incluirá un formulario que permitirá al usuario introducir datos complejos y utilizará Ajax para enviar esos datos al servidor.
@{
ViewData["Title"] = "Save Person";
}
<h2>Person Form</h2>
<form id="personForm">
<div>
<label>First Name:</label>
<input type="text" id="firstName" />
</div>
<div>
<label>Last Name:</label>
<input type="text" id="lastName" />
</div>
<div>
<h3>Address</h3>
<label>Street:</label>
<input type="text" id="street" />
<label>City:</label>
<input type="text" id="city" />
<label>State:</label>
<input type="text" id="state" />
<label>Zip Code:</label>
<input type="text" id="zipCode" />
</div>
<div>
<h3>Phone Numbers</h3>
<label>Type:</label>
<input type="text" id="phoneType" />
<label>Number:</label>
<input type="text" id="phoneNumber" />
<button type="button" id="addPhone">Add Phone</button>
</div>
<div>
<ul id="phoneList"></ul>
</div>
<button type="submit">Save Person</button>
</form>
<div id="result"></div>
@section Scripts {
<script type="text/javascript">
$(document).ready(function () {
var phoneNumbers = [];
$('#addPhone').click(function () {
var phoneType = $('#phoneType').val();
var phoneNumber = $('#phoneNumber').val();
if (phoneType && phoneNumber) {
phoneNumbers.push({ Type: phoneType, Number: phoneNumber });
$('#phoneList').append('<li>' + phoneType + ': ' + phoneNumber + '</li>');
$('#phoneType').val('');
$('#phoneNumber').val('');
} else {
alert('Please enter both phone type and number.');
}
});
$('#personForm').submit(function (e) {
e.preventDefault();
var personData = {
FirstName: $('#firstName').val(),
LastName: $('#lastName').val(),
Address: {
Street: $('#street').val(),
City: $('#city').val(),
State: $('#state').val(),
ZipCode: $('#zipCode').val()
},
PhoneNumbers: phoneNumbers
};
$.ajax({
url: '/api/person/SavePerson',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(personData),
success: function (response) {
$('#result').html('<p style="color: green;">' + response.message + '</p>');
},
error: function () {
$('#result').html('<p style="color: red;">An error occurred while saving the person data.</p>');
}
});
});
});
</script>
}
Explicación Detallada de la Vista
La vista SavePerson.cshtml
está diseñada para capturar información detallada sobre una persona, incluyendo dirección y números de teléfono, y enviar esos datos al servidor utilizando Ajax. Vamos a desglosar cada parte de esta vista para entender su funcionamiento:
El formulario HTML
El formulario HTML incluye campos para el nombre, apellido, dirección y números de teléfono:
<h2>Person Form</h2>
<form id="personForm">
<div>
<label>First Name:</label>
<input type="text" id="firstName" />
</div>
<div>
<label>Last Name:</label>
<input type="text" id="lastName" />
</div>
<div>
<h3>Address</h3>
<label>Street:</label>
<input type="text" id="street" />
<label>City:</label>
<input type="text" id="city" />
<label>State:</label>
<input type="text" id="state" />
<label>Zip Code:</label>
<input type="text" id="zipCode" />
</div>
<div>
<h3>Phone Numbers</h3>
<label>Type:</label>
<input type="text" id="phoneType" />
<label>Number:</label>
<input type="text" id="phoneNumber" />
<button type="button" id="addPhone">Add Phone</button>
</div>
<div>
<ul id="phoneList"></ul>
</div>
<button type="submit">Save Person</button>
</form>
<div id="result"></div>
Aquí tienes una representación visual de cómo se vería la vista SavePerson
en un navegador web:
-------------------------------------------------
| <h2>Person Form</h2> |
-------------------------------------------------
| First Name: [_____________________________] |
-------------------------------------------------
| Last Name: [_____________________________] |
-------------------------------------------------
| Address |
| ------------------------------------------- |
| Street: [_____________________________] |
| City: [_____________________________] |
| State: [_____________________________] |
| Zip Code: [_____________________________] |
-------------------------------------------------
| Phone Numbers |
| ------------------------------------------- |
| Type: [_____________________________] |
| Number: [_____________________________] |
| [Add Phone] |
| ------------------------------------------- |
| Phone List: |
| - Mobile: 123-456-7890 |
| - Home: 098-765-4321 |
-------------------------------------------------
| [Save Person] |
-------------------------------------------------
| <div id="result"></div> |
| |
-------------------------------------------------
En esta vista visual, puedes ver cómo se estructuran los campos del formulario y cómo se representan los números de teléfono agregados en la lista Phone List
.
El Script jQuery
El script jQuery se encarga de manejar la lógica para agregar números de teléfono a una lista y enviar los datos del formulario al servidor:
$(document).ready(function () {
var phoneNumbers = [];
$('#addPhone').click(function () {
var phoneType = $('#phoneType').val();
var phoneNumber = $('#phoneNumber').val();
if (phoneType && phoneNumber) {
phoneNumbers.push({ Type: phoneType, Number: phoneNumber });
$('#phoneList').append('<li>' + phoneType + ': ' + phoneNumber + '</li>');
$('#phoneType').val('');
$('#phoneNumber').val('');
} else {
alert('Please enter both phone type and number.');
}
});
$('#personForm').submit(function (e) {
e.preventDefault();
var personData = {
FirstName: $('#firstName').val(),
LastName: $('#lastName').val(),
Address: {
Street: $('#street').val(),
City: $('#city').val(),
State: $('#state').val(),
ZipCode: $('#zipCode').val()
},
PhoneNumbers: phoneNumbers
};
$.ajax({
url: '/api/person/SavePerson',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(personData),
success: function (response) {
$('#result').html('<p style="color: green;">' + response.message + '</p>');
},
error: function () {
$('#result').html('<p style="color: red;">An error occurred while saving the person data.</p>');
}
});
});
});
-
$(document).ready():
- Esta función asegura que el código dentro de ella se ejecute solo después de que el DOM esté completamente cargado.
-
Array
phoneNumbers
:- Declaramos un array vacío
phoneNumbers
para almacenar los números de teléfono ingresados por el usuario.
- Declaramos un array vacío
-
Agregar Números de Teléfono:
- La función de clic en
#addPhone
se ejecuta cuando el usuario hace clic en el botón "Add Phone". Recopila el tipo de teléfono y el número, los agrega al arrayphoneNumbers
y los muestra en la lista#phoneList
. - Si alguno de los campos está vacío, muestra una alerta pidiendo al usuario que ingrese ambos datos.
- La función de clic en
-
Manejar el Envío del Formulario:
- La función de envío del formulario
#personForm
se ejecuta cuando el usuario envía el formulario. - Primero, evita el comportamiento predeterminado del formulario con
e.preventDefault()
. - Luego, recopila todos los datos del formulario, incluidos los números de teléfono, en un objeto
personData
. - Utiliza
$.ajax
para enviar una solicitud POST al servidor con los datos del formulario en formato JSON. - En caso de éxito, muestra un mensaje en
#result
con el contenido de la respuesta del servidor. - En caso de error, muestra un mensaje de error en
#result
.
- La función de envío del formulario
Nota: Este script proporciona una interacción fluida y en tiempo real con el servidor, permitiendo a los usuarios agregar múltiples números de teléfono y enviar todos los datos en una sola solicitud Ajax.
Optimización y Buenas Prácticas
Para mejorar aún más esta implementación, considera lo siguiente:
Validación del Lado del Cliente
Es fundamental validar los datos del formulario del lado del cliente antes de enviarlos al servidor para asegurar que están completos y en el formato esperado. Esto mejora la experiencia del usuario al proporcionar retroalimentación instantánea y reduce la carga en el servidor al evitar solicitudes innecesarias con datos inválidos.
$('#personForm').submit(function (e) {
e.preventDefault();
var isValid = true;
// Validación básica de campos requeridos
$('input').each(function() {
if ($(this).val().trim() === '') {
isValid = false;
$(this).addClass('is-invalid'); // Marca los campos inválidos visualmente
} else {
$(this).removeClass('is-invalid');
}
});
if (!isValid) {
$('#formFeedback').html('<div class="alert alert-danger">Por favor, complete todos los campos requeridos.</div>');
return;
}
// Proceder con la solicitud Ajax si todos los campos son válidos
var personData = {
FirstName: $('#firstName').val(),
LastName: $('#lastName').val(),
Address: {
Street: $('#street').val(),
City: $('#city').val(),
State: $('#state').val(),
ZipCode: $('#zipCode').val()
},
PhoneNumbers: phoneNumbers
};
$.ajax({
url: '/api/person/SavePerson',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(personData),
success: function (response) {
$('#result').html('<p style="color: green;">' + response.message + '</p>');
},
error: function () {
$('#result').html('<p style="color: red;">Se produjo un error al guardar los datos de la persona.</p>');
}
});
});
Explicación:
-
Validación Básica: Recorremos todos los campos de entrada (
input
) y verificamos si están vacíos (trim() === ''
). Si un campo está vacío, marcamos ese campo como inválido visualmente añadiendo la claseis-invalid
. -
Mensaje de Retroalimentación: Si se encuentra un campo inválido, se muestra un mensaje de alerta en
#formFeedback
indicando al usuario que complete todos los campos requeridos. -
Envío de Datos: Si todos los campos son válidos, se procede con la solicitud Ajax para enviar los datos al servidor.
Manejo de Errores y Validación en el Servidor
Es esencial implementar un manejo robusto de errores en el servidor para proporcionar mensajes claros y significativos al usuario en caso de problemas durante el procesamiento de datos en el servidor.
Agregar Validación
Añade atributos de validación a las clases de Modelo Person
, Address
y PhoneNumber
utilizando System.ComponentModel.DataAnnotations
. Esto asegura que los datos enviados al servidor son válidos y completos.
public class Person
{
public int Id { get; set; }
[Required(ErrorMessage = "First Name is required")]
[StringLength(40, ErrorMessage = "First Name cannot be longer than 40 characters")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is required")]
public string LastName { get; set; }
public Address Address { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
}
public class Address
{
[Required(ErrorMessage = "Street is required")]
[StringLength(100, ErrorMessage = "Street cannot be longer than 100 characters")]
public string Street { get; set; }
[Required(ErrorMessage = "City is required")]
public string City { get; set; }
[Required(ErrorMessage = "State is required")]
public string State { get; set; }
[Required(ErrorMessage = "Zip Code is required")]
public string ZipCode { get; set; }
}
public class PhoneNumber
{
[Required(ErrorMessage = "Phone Type is required")]
public string Type { get; set; }
[Required(ErrorMessage = "Phone Number is required")]
public string Number { get; set; }
}
El siguiente código maneja errores potenciales al procesar datos en el servidor (Controlador Web API PersonController
), devolviendo un código de estado HTTP adecuado y un mensaje de error detallado si algo sale mal durante la ejecución.
[HttpPost]
[Route("SavePerson")]
public IActionResult SavePerson([FromBody] Person person)
{
if (person == null)
{
return BadRequest(new { success = false, message = "Datos de persona inválidos." });
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
// Aquí se procesan los datos del modelo 'Person', por ejemplo, guardarlos en una base de datos
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "Se produjo un error al procesar los datos.", error = ex.Message });
}
return Ok(new { success = true, message = "Datos de persona recibidos exitosamente." });
}
Explicación:
-
Validación del Modelo:
ModelState.IsValid
verifica si el modeloPerson
pasado desde el cuerpo de la solicitud (FromBody
) cumple con las reglas de validación definidas en el modelo (comoRequired
,StringLength
, etc.). Si no cumple con estas reglas, se devuelven errores de validación detallados.
-
Manejo de Excepciones: Dentro del bloque
try-catch
, se procesan los datos del modeloPerson
. Cualquier excepción que ocurra durante este proceso se captura en el bloquecatch
y se devuelve una respuesta con un código de estado 500 (Error interno del servidor) junto con un mensaje de error detallado.
Seguridad y Mejoras Adicionales
Para mejorar aún más la robustez y la seguridad de tu aplicación, considera implementar las siguientes prácticas:
-
Autenticación y Autorización: Implementa mecanismos de autenticación y autorización adecuados para proteger tu API y los datos del usuario. Puedes utilizar tokens JWT (JSON Web Tokens) para autenticación basada en tokens y configurar políticas de autorización para controlar el acceso a recursos específicos.
-
SSL/TLS: Asegúrate de que todas las comunicaciones entre el cliente y el servidor estén protegidas utilizando HTTPS (SSL/TLS). Esto ayuda a proteger los datos confidenciales durante la transmisión.
-
Pruebas Unitarias y de Integración: Escribe pruebas unitarias para verificar la funcionalidad de tu API y las reglas de validación del modelo. Utiliza bibliotecas como xUnit, NUnit para pruebas unitarias y Moq para simular el comportamiento del cliente en pruebas de integración.
-
Logging: Implementa un sistema de registro (logging) para registrar eventos importantes y errores en la aplicación. Esto te ayudará a diagnosticar problemas y mejorar la monitorización del sistema.
Nuevo comentario
Comentarios
No hay comentarios para este Post.