sábado, 1 de febrero de 2020

ASP.NET Core - Crear Web API con C# y EF

Que tal a todos, hoy quiero compartirles acerca de la creación de Web API con C# y EF, bueno comencemos con algo de teoría. 


API significa interfaz de programación de aplicaciones, es decir es un conjunto de definiciones y protocolos que se utiliza para desarrollar e integrar entre software de distintas aplicaciones, que nos permiten que sus productos y servicios se comuniquen con otros (intermedio), sin necesidad de saber cómo están implementados. Al hacer uso de las API's en el desarrollo de las aplicaciones, nos permite ahorrar tiempo y dinero. Las API nos otorgan flexibilidad; simplifican el diseño e implementación, la administración y el uso de las aplicaciones, y proporcionan oportunidades de innovación e independencia, lo cual es ideal al momento de diseñar nuevos productos.

Características de WEB API
  • Admite acciones CRUD basadas en convenciones, ya que funciona con los verbos HTTP GET, POST, PUT y DELETE.
  • Las respuestas tienen un encabezado de Aceptar y un código de estado HTTP.
  • Las respuestas están formateadas por MediaTypeFormatter de WEB API en JSON, XML o cualquier formato que desee agregar como MediaTypeFormatter.
  • Puede aceptar y generar el contenido que puede no estar orientado a objetos como imágenes, archivos PDF, etc.
  • Tiene soporte automático para OData. Por lo tanto, colocando el nuevo atributo [Queryable] en un método de controlador que devuelve IQueryable, los clientes pueden usar el método para la composición de consulta OData.
  • Puede alojarse en la aplicación o en IIS, JBOSS, WAS u Otros Servidores.
  • También es compatible con las características de MVC, tales como enrutamiento, controladores, resultados de acciones, filtro, carpetas de modelo, contenedor IOC o inyección de dependencia que lo hace más simple y robusto.
¿Por qué elegir WEB API?
  • Si necesitamos un servicio web y no necesitamos SOAP, entonces ASP.Net (u ASP.Net CORE) Web API es la mejor opción.
  • Se utiliza para crear servicios HTTP simples, que no estén basados ​​en SOAP, en la parte superior de la canalización de mensajes de WCF existente.
  • No tiene una configuración tediosa y extensa como el servicio WCF REST.
  • Creación de servicio simple con WEB API. Con WCF REST Services, la creación de servicios es difícil.
  • Solo se basa en HTTP y es fácil de definir, exponer y consumir de manera REST-ful.
  • Es una arquitectura liviana y buena para dispositivos que tienen ancho de banda limitado como teléfonos inteligentes.
  • Es de código abierto.
Sugiero que revisen más conceptos que hay muchísima información al respecto, ahora que conocemos algunos conceptos acerca de las web api iniciemos con nuestro primer demo:

Requisitos para nuestro primer demo:
Microsoft Visual Studio Community 2019
.Net Core 3.1
Microsoft Entity Framework Core
Postman

Crear Solución:
Se puede crear directamente el proyecto y al mismo tiempo se crear la solución, sin embargo para familiarizarnos con la nueva versión del IDE VS2019 crearemos paso a paso la solución y respectivamente el proyecto.

Selecciona solución en blanco y clic en siguiente
Ingresa el nombre de WEB_API_CORE y clic en Crear
Crear Proyecto Web API:
Seguidamente clic derecho en la solución y seleccionar agregar nuevo proyecto, y ubicamos el tipo de proyecto Aplicación web ASP.NET Core.

Seleccionamos Aplicación web ASP.NET Core y clic en Siguiente
Ingresamos el nombre y clic en Crear
Seleccionar ASP.NET Core 3.1 y para finalizar clic en Crear
Crear Modelo:
Para crear nuestro modelo, se agregará la carpeta de nombre Models en la raíz del proyecto creado (clic derecho, agregar carpeta):

Carpeta creado
Luego se agregará una clase de nombre ClsPersona.cs (clic derecho en la carpeta Models y agregar clase)
Seguidamente se agregara los siguiente atributos en la clase (representa el modelo Persona) ClsPersona.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WEB_API_CORE.Models
{
    public class ClsPersona
    {
        public long id { get; set; }
        public string nombre { get; set; }
        public string apellido { get; set; }
        public string sexo { get; set; }
        public string dni { get; set; }
    }
}

Microsoft Entity Framework Core InMemory:
Qué es Microsoft.EntityFrameworkCore.InMemory: Proveedor de base de datos que permite usar Entity Framework Core con una base de datos en memoria, que sirve especialmente para realizar pruebas.

Instalar EF Core InMemory: Desde la consola de administración de paquetes de NuGet, clic en el menú herramientas, administración de paquetes NuGet y por último clic en consola del administración de paquetes e ingresar las siguiente línea Install-Package Microsoft.EntityFrameworkCore.InMemory -Version 3.1.1

Instalando EF Core InMemory
Microsoft Entity Framework Core SqlServer:
Qué es Microsoft.Entity.Framework.Core.SqlServer: Proveedor de base de datos que permite usar Entity Framework Core con Microsoft SQL Server incluido SQL Azure:

Instalar EF Core SqlServer: Desde el administración de paquetes NuGet para solución, clic en el menú herramientas, administración de paquetes NuGet y por último clic en administración de paquetes NuGet para solución... realiza la búsqueda desde la pestaña examinar e ingresar Microsoft.EntityFrameworkCore.SqlServer, después de ubicar clic en proyecto y clic en instalar.

Instalando EF Core SqlServer
Al concluir la instalación de Microsoft.Entity.Framework.Core.SqlServer automáticamente se creara el ya conocido CRUD, es decir se crearan los siguiente métodos en la clase  Controllers (ClsPersonasController.cs):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WEB_API_CORE.Models;

namespace WEB_API_CORE.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ClsPersonasController : ControllerBase
    {
        private readonly ClsPersonaContext _context;

        public ClsPersonasController(ClsPersonaContext context)
        {
            _context = context;
        }

        // GET: api/ClsPersonas
        [HttpGet]
        public async Task<ActionResult<IEnumerable<ClsPersona>>> GetClsPersonas()
        {
            return await _context.ClsPersonas.ToListAsync();
        }

        // GET: api/ClsPersonas/5
        [HttpGet("{id}")]
        public async Task<ActionResult<ClsPersona>> GetClsPersona(long id)
        {
            var clsPersona = await _context.ClsPersonas.FindAsync(id);

            if (clsPersona == null)
            {
                return NotFound();
            }

            return clsPersona;
        }

        // PUT: api/ClsPersonas/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see https://aka.ms/RazorPagesCRUD.
        [HttpPut("{id}")]
        public async Task<IActionResult> PutClsPersona(long id, ClsPersona clsPersona)
        {
            if (id != clsPersona.id)
            {
                return BadRequest();
            }

            _context.Entry(clsPersona).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ClsPersonaExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        // POST: api/ClsPersonas
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see https://aka.ms/RazorPagesCRUD.
        [HttpPost]
        public async Task<ActionResult<ClsPersona>> PostClsPersona(ClsPersona clsPersona)
        {
            _context.ClsPersonas.Add(clsPersona);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetClsPersona"new { id = clsPersona.id }, clsPersona);
        }

        // DELETE: api/ClsPersonas/5
        [HttpDelete("{id}")]
        public async Task<ActionResult<ClsPersona>> DeleteClsPersona(long id)
        {
            var clsPersona = await _context.ClsPersonas.FindAsync(id);
            if (clsPersona == null)
            {
                return NotFound();
            }

            _context.ClsPersonas.Remove(clsPersona);
            await _context.SaveChangesAsync();

            return clsPersona;
        }

        private bool ClsPersonaExists(long id)
        {
            return _context.ClsPersonas.Any(e => e.id == id);
        }
    }
}

Todo esto ocurre porque se esta haciendo uso del ORM (Object-relational mapper) Entity Framework nos olvidamos de estar creando manualmente nuestro código (una buena alternativa para realizar pruebas - en lo personal no uso mucho ORMs prefiero hacer uso de ADO.Net).

Testear con Postman:
Postman permite el envío de peticiones HTTP sin necesidad de desarrollar un cliente, iniciemos  nuestro test:
Ingresamos los datos con e verbs de tipo POST, ingresamos los datos en el Body del application/json y clic en Send
Consultamos los datos con el verbs de tipo GET, e ingresamos 1 en la url ..api/ClsPeronas/1  y clic en Send
Modifica los datos con el verbs de tipo PUT, modifica el nombre en el Body del application/json y clic en Send
Puede seguir implementando y testeando los otros método de petición HTTP verbs y recordemos los métodos más relevantes son:

POST: crear un recurso nuevo.
PUT: modificar un recurso existente.
GET: consultar información de un recurso.
DELETE: eliminar un recurso determinado.
PATCH: modificar solamente un atributo de un recurso.

Conclusión:
En definitivo las Web APIs no es complicado de implementar, y la flexibilidad que nos ofrece es su principal ventaja y uso que reside en la independencia que proporciona frente a cualquier consumidor, sin considerar el lenguaje o plataforma con el que se acceda a ella. Permitiendo que una misma API REST (también se puede resolver de tipo XML) sea consumida por infinidad de clientes sea cual sea la naturaleza de estos y que el cambio a cualquier otro tipo de consumidor no provoque impacto alguno.

Pronto podrás descargar la fuente desde:
Nuevamente agradecer de antemano por la revisión y lectura de este pequeño post, nos vemos en el siguiente tutorial.

viernes, 31 de enero de 2020

ASP.NET Core - PAGINACIÓN en MVC con C#

Hola que tal, hoy quiero compartirles acerca de  la paginación en ASP.NET Core para este demo estoy considerando el CRUD elaborado en ASP.NET Core - Implementando CRUD en MVC con C#, haré uso de la tabla usuario creado en la base de datos BD_SEGURIDAD.

Requisitos:
Microsoft Visual Studio Community 2019
Microsoft .Net Framework
Microsoft SQL Server 2017 (RTM-GDR) - Express Edition (64-bit)
Framework Bootstrap (para mejorar el diseño de nuestra aplicación)
Framework  jQuery Validation (para la validación de lado del cliente)

Crear el procedimiento almacenado:
Crearemos el procedimiento almacenado de nombre GET_USUARIO en nuestro base de datos BD_SEGURIDAD. 

CREATE PROCEDURE GET_USUARIO
(
       @OffsetValue int,
       @PagingSize int
)
AS
BEGIN
       SELECT Id, Usuario, Contrasena, Intentos, NivelSeg, FechaReg FROM USUARIO ORDER BY Id
       OFFSET @OffsetValue ROWS FETCH NEXT @PagingSize ROWS ONLY;

       Select count(Id) as TotalRows from USUARIO
END

Nos conectamos a SQL desde el IDE de Visual Studio, clic derecho y clic en Agregar nuevo procedimiento almacenado, pegar el script en la sección sql y clic en Actualizar
Verificación de la tabla creada

Lo procedimiento almacenado cuenta con 2 parámetros de entrada, dichos parámetros sirve para poder definir el rango de registros a seleccionar - es decir si defino al parámetro @PagingSize con valor 5 voy a mostrar en tabla o grilla en grupo de 5 registros:

       *SELECT Id, Usuario, Contrasena, Intentos, NivelSeg, FechaReg FROM USUARIO ORDER BY Id
       OFFSET @OffsetValue ROWS FETCH NEXT @PagingSize ROWS ONLY;

*Donde mis parámetros iniciales sería @OffsetValue = 0 y PagingSize = 5

       **Select count(Id) as TotalRows from USUARIO

**Cuento la cantidad de registro existente y retorno en la parámetro TotalRows de tipo de salida.

Agregar key en appsettings.json:
Crearemos un nuevo key de nombre PageZise con el valor 5, es decir nuestro rango de registros a seleccionar será de 5 en 5.

  "KeyConfig": {
    "PageSize": "5"
  }

Recuerde que la cadena de conexión se mantiene en el archivo appsettings.json. 

Para acceder a este archivo (appsettings.json.), se debe obtener el objeto de la interfaz IConfiguration a través de la función Inyección de dependencias. Por lo tanto, se agrega la interfaz IConfiguration en el constructor de HomeController y el patrón del MVC que nos proporcionará automáticamente el objeto.

Agregar IActionResult paginado:
Ahora crearemos la funcionalidad de paginado, para cual agregaremos 2 nuevos IActionResult. Por lo tanto, agregue los IActionResult de  la acción Create al Home Controller como se muestra a continuación:

La explicación del código en los comentarios del código

        public IActionResult Usuario() {
            return View();
        }

        [HttpGet]
        [ActionName("Usuario")]
        public IActionResult UsuarioPage(int page = 1)
        {
            //capturamos el valor PageSize, esta definido como 5
            int PageSize = Convert.ToInt16(Configuration["KeyConfig:PageSize"]);
            //creamos el objeto ViewModel
            UsuarioViewModel _usuarioViewModel = new UsuarioViewModel();
            DataSet ds = new DataSet();
            //listamos los campos de usuario
            List<ClsUsuario> ListclsUsuarios = new List<ClsUsuario>();

            //Conexión a la base de datos
            string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
            //Estoy usando uso de ADO.Net para interactuar con la base de datos, puede usar cualquier otros ORM
            using (SqlConnection connection = new SqlConnection(connectionString))
            //using (SqlConnection con = new SqlConnection(Configuration["ConnectionStrings:DefaultConnection"]))
            {
                connection.Open();
                SqlCommand com = new SqlCommand("GET_USUARIO", connection);
                com.CommandType = CommandType.StoredProcedure;
                //Paso los parametros para seleccionar el rango
                com.Parameters.AddWithValue("@OffsetValue", (page - 1) * PageSize);
                com.Parameters.AddWithValue("@PagingSize", PageSize);
                SqlDataAdapter adapt = new SqlDataAdapter(com);
                //Cargo el conjunto de datos con fill y cierro la conexión a base de datos
                adapt.Fill(ds);
                connection.Close();
                //Ahora asocio los datos de usuario
                //Estamos retornando 2 conjunto de datos, una contiene los datos del usuario y otra contiene la cantidad total de registros existente en la tabla usuario
                if (ds != null && ds.Tables.Count == 2)
                {
                    for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
                    {
                        ClsUsuario clsUsuario = new ClsUsuario();
                        clsUsuario.id = Convert.IsDBNull(ds.Tables[0].Rows[i]["Id"]) ? 0 : Convert.ToInt32(ds.Tables[0].Rows[i]["Id"]);
                        clsUsuario.usuario = Convert.IsDBNull(ds.Tables[0].Rows[i]["Usuario"]) ? "" : Convert.ToString(ds.Tables[0].Rows[i]["Usuario"]);
                        clsUsuario.contrasena = Convert.IsDBNull(ds.Tables[0].Rows[i]["Contrasena"]) ? "" : Convert.ToString(ds.Tables[0].Rows[i]["Contrasena"]);
                        clsUsuario.intentos = Convert.IsDBNull(ds.Tables[0].Rows[i]["Intentos"]) ? 0 : Convert.ToInt32(ds.Tables[0].Rows[i]["Intentos"]);
                        clsUsuario.nivelSeg = Convert.IsDBNull(ds.Tables[0].Rows[i]["NivelSeg"]) ? 0 : Convert.ToDecimal(ds.Tables[0].Rows[i]["NivelSeg"]);
                        clsUsuario.fechaReg = Convert.IsDBNull(ds.Tables[0].Rows[i]["FechaReg"]) ? (DateTime?)null : Convert.ToDateTime(ds.Tables[0].Rows[i]["FechaReg"]);
                        ListclsUsuarios.Add(clsUsuario);
                    }
                    //Pasamos el total de registros para la página actual
                    var pager = new Pager((ds.Tables[1] != null && ds.Tables[1].Rows.Count > 0) ? Convert.ToInt32(ds.Tables[1].Rows[0]["TotalRows"]) : 0, page, PageSize);
                    _usuarioViewModel.ListUsuario = ListclsUsuarios;
                    _usuarioViewModel.pager = pager;
                }
            }
            return View(_usuarioViewModel);

        }

Agregar Views paginado:
A continuación, cree una nuevo view llamada Usuario.cshtml dentro de esta carpeta Home (es decir, Views/Home) y agregue el siguiente código a esta Vista:

@model DEMO01_CRUD.Models.UsuarioViewModel
@{
    var title = "PAGINACION CON ADO.NET";
    ViewData["Title"] = title;
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="row justify-content-center">
    <h4>@title</h4>
</div>

<p>
    <a asp-action="Create">Create New</a>
</p>
<div class="container">
    <table class="table table-bordered">
        <thead>
            <tr>
                <th>Id</th>
                <th>Usuario</th>
                <th>Contraseña</th>
                <th>Intentos</th>
                <th>Nivel Seguridad</th>
                <th>Fecha Registro</th>
                @*<th>Botones</th>*@
            </tr>
        </thead>
        <tbody>
            @if (Model.ListUsuario != null)
            {
                for (int i = 0; i < Model.ListUsuario.Count; i++)
                {
            <tr>
                <td>@Model.ListUsuario[i].id</td>
                <td>@Model.ListUsuario[i].usuario</td>
                <td>@Model.ListUsuario[i].contrasena</td>
                <td>@Model.ListUsuario[i].intentos</td>
                <td>@Model.ListUsuario[i].nivelSeg</td>
                <td>@string.Format("{0:dd/MM/yyyy}", @Model.ListUsuario[i].fechaReg)</td>
                @*<td>@string.Format("{0:dddd, dd MMMM yyyy}", @Model.ListUsuario[i].fechaReg)</td>*@
                <td>
                    @Html.ActionLink("Edit", "Edit", new { id = @Model.ListUsuario[i].id }) |
                    @Html.ActionLink("Details", "Details", new { id = @Model.ListUsuario[i].id }) |
                    @Html.ActionLink("Delete", "Delete", new { id = @Model.ListUsuario[i].id })
                </td>
            </tr>
                }
            }
        </tbody>
    </table>

    <div class="row justify-content-center">
        <!-- paginación -->
        @if (Model.pager.EndPage > 1)
        {
        <ul class="pagination">

            @if (Model.pager.CurrentPage > 1)
            {
                <li>
                    <a href="~/Home/Usuario">Primero</a>
                </li>
                <li>
                    <a href="~/Home/Usuario?page=@(Model.pager.CurrentPage - 1)">Anterior</a>
                </li>
            }
            else if (Model.pager.CurrentPage == 1)
            {
                <li>
                    <a href="~/Home/Usuario">Primero</a>
                </li>
            }

            <!--Paginación activa-->
            @for (var p = Model.pager.StartPage; p <= Model.pager.EndPage; p++)
            {
                <li class="@(p == Model.pager.CurrentPage ? "active" : "")">
                    <a href="~/Home/Usuario?page=@p">@p</a>
                </li>
            }

            @if (Model.pager.CurrentPage < Model.pager.TotalPages)
            {
                <li>
                    <a href="~/Home/Usuario?page=@(Model.pager.CurrentPage + 1)">Siguiente</a>
                </li>
                <li>
                    <a href="~/Home/Usuario?page=@(Model.pager.TotalPages)">Último</a>
                </li>
            }
            else if (Model.pager.CurrentPage == Model.pager.TotalPages)
            {
                <li>
                    <a href="~/Home/Usuario?page=@(Model.pager.TotalPages)">Último</a>
                </li>
            }
        </ul>
        }
    </div>

Modificar enrutamiento de action:
Ahora cambiemos el enrutamiento de  action a usuario, para lo cual ingresa a la clase Startup.cs:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseStaticFiles();//facilita el uso de los archivos de web root ( wwwroot por defecto)
            app.UseDeveloperExceptionPage();//captura las instancias de excepción y genera respuesta de error
            app.UseMvc(router =>
            {
                router.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Usuario}/{id?}");
            });
        }

Ejecutar la implementación:
Para finalizar ejecutamos la implementación, en el navegador carga los 5 primeros registro. Puedes seguir agregando datos y ver la funcionalidad completa de lo implementado.

Inicialmente cargaremos 5 registros, clic en Siguiente
Cargaremos el paginado 2

Conclusión:
La paginacion en sitios web es la forma de estructurar y agrupar el contenido, agrupándolo por una cantidad fija de espacio o cantidad de elementos. Hay muchas formar de implementarlas, pero el resultado siempre será los mismo - en nuestro caso nos apoyamos en ADO.Net bajo la tecnología de ASP.Net Core y el resultado de la paginación es simplemente el número de páginas que se muestran en la parte inferior de la tabla de resultados que sirve para separar y distribuir el contenido y facilitar la navegación.

Pronto podrás descargar la fuente desde:
 Descargar fuente de ASP.NET-Core-CRUD-MVC
Agradecer de antemano por la revisión y lectura de este pequeño apartado, nos vemos en el siguiente tutorial.