Cómo crear un cliente C# para un Web API de ASP.NET Core (II)

En el anterior post de este blog Cómo crear un cliente C# para un Web API de ASP.NET Core (I), vimos cómo implementar un cliente C# con la ayuda de NSwag Studio, para consumir un Web API de ASP.NET Core con autenticación de usuarios mediante JSON Web Tokens (JWT).

En este artículo, y como continuación al post anterior, veremos cómo integrar este cliente C# en una aplicación ASP.NET Core MVC, que nos permita realizar las operaciones básicas de CRUD sobre el Web API en cuestión.

webapi-client

 

Para comenzar recuperaremos la solución que contiene el proyecto WebApiClient.csproj que ya creamos en el anterior artículo Cómo crear un cliente C# para un Web API de ASP.NET Core (I)

A continuación y en la misma solución, crearemos un nuevo proyecto del tipo Aplicación Web ASP.NET Core MVC llamado AspNetCoreClient.csproj. Este proyecto será sobe el cual trabajaremos, así que lo primero que debemos hacer es añadir una referencia al cliente C# (proyecto WebApiClient.csproj) desde la sección Dependencias, botón derecho del ratón, Agregar referencia... .

 

Consideraciones previas

Antes de comenzar a crear los Controladores y las Vistas de nuestra aplicación, realizaremos una serie de configuraciones que posteriormente utilizaremos en el desarrollo.

La autenticación de usuarios

Como ya sabemos, el Web API que consumiremos desde nuestra aplicación, requiere la autenticación de usuarios mediante JSON Web Tokens (JWT).

Un token JWT, además de permitirnos el acceso al Web API, contiene también cierta información que nos puede ser de mucha utilidad a la hora de desarrollar nuestra aplicación. Si lo decodificamos, podemos a acceder a la información del usuario que lo solicitó (Claims), así como la fecha de caducidad del propio token.

Es por esto que crearemos una nueva clase de Modelo UsuarioInfo.cs , la cual contendrá toda la información relativa al usuario y al token que necesitemos. El código sería el siguiente:

    public class UsuarioInfo
    {
        public Guid Id { get; set; }
        public string Nombre { get; set; }
        public string Apellidos { get; set; }
        public string Email { get; set; }
        public string Rol { get; set; }
        public DateTime ValidoDesde { get; set; }
        public DateTime ValidoHasta { get; set; }
    }

A continuación crearemos una nueva clase de Servicio JwtTokenService.cs , la cual se encargará de decodificar el token JWT de acceso, y devolverá un objeto del tipo UsuarioInfo con la información del usuario. El código sería el siguiente:

    public class JwtTokenService : IJwtTokenService
    {
        public UsuarioInfo DecodeJwtToken(string token)
        {
            // EXTRAE LA INFORMACIÓN DEL TOKEN JWT.
            var handler = new JwtSecurityTokenHandler();            
            var _token = handler?.ReadJwtToken(token);

            // CREA EL PERFIL DE INFORMACIÓN DEL USUARIO
            // A PARTIR DE LOS CLAIMS DEL TOKEN JWT
            var _usuarioInfo = new UsuarioInfo()
            {
                Id = new Guid(_token?.Claims?.
                    SingleOrDefault(x => x.Type == "nameid")?.Value 
                    ?? _token.Id),

                Nombre = _token?.Claims?.
                    SingleOrDefault(x => x.Type == "nombre")?.Value,

                Apellidos = _token?.Claims?.
                    SingleOrDefault(x => x.Type == "apellidos")?.Value,

                Email = _token?.Claims?.
                    SingleOrDefault(x => x.Type == "email")?.Value,

                Rol = _token?.Claims?.
                    SingleOrDefault(x => x.Type.Contains("role"))?.Value,

                ValidoDesde = _token.ValidFrom,

                ValidoHasta = _token.ValidTo
            };
            return _usuarioInfo;
        }
    }

    // CREAMOS LA INTERFAZ DE LA CLASE, PARA PODER 
    // INYECTARLA POR DEPENDENCIAS.
    public interface IJwtTokenService
    {
        UsuarioInfo DecodeJwtToken(string token);
    }

La inyección de dependencias

Si ya le hemos echado un vistazo a nuestro cliente de Web API WebApiClient.dll, observaremos que las dos clases cliente principales LoginClientPaisClient requieren la inyección de un objeto del tipo HttpClient a través de su constructor.

A su vez, ambas clases, deberán también ser inyectadas en los Controladores de nuestra aplicación ASP.NET MVC para poder ser utilizadas. 

Afortunadamente ASP.NET Core nos permite solucionar estas dos necesidades de una sola atacada, mediante el método extensor AddHttpClient<> de la colección IServiceCollection. Para ello, accederemos al archivo Startup.cs de la aplicación, y añadiremos el siguiente código al método ConfigureServices(IServiceCollection services):

        // This method gets called by the runtime. 
        // Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // ...

            // SUMINISTRA LOS CLIENTES LoginClient Y PaisClient Y ADEMÁS
            // LES INYECTA POR CONSTRUCTOR UN HttpClient A CADA UNO.
            services.AddHttpClient<WebApiClient.ILoginClient, WebApiClient.LoginClient>();
            services.AddHttpClient<WebApiClient.IPaisClient, WebApiClient.PaisClient>();

            // SUMINISTRA UNA INSTANCIA DE LA CLASE DE SERVICIO JwtTokenService.
            services.AddScoped<IJwtTokenService, JwtTokenService>();

            // ...

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

Nota: Como podemos ver en el código, también hemos configurado la inyección de dependencias para el Servicio JwtTokenService, con el objetivo de poder acceder a una instancia del mismo a través del constructor de los Controladores MVC.

 

Los Controladores

En este punto, ya podremos comenzar a crear los Controladores de nuestra aplicación ASP.NET Core MVC. 

Como ya hemos visto, nuestro cliente de Web API WebApiClient expone dos clases cliente principales (WebApiClient.LoginClientWebApiClient.PaisClient). Siguiendo esta notación, nosotros crearemos entonces un Controlador para cada una de ellas (LoginController.csPaisController.cs).

El Controlador LoginController

Antes de exponer el código del Controlador LoginController.cs, es conveniente explicar una serie de puntos que serán comunes a todos los Controladores de nuestra aplicación.

La inyección de dependencias a través del constructor

Como ya hemos comentado, utilizaremos la inyección de dependencias nativa de ASP.NET Core, para suministrar a través del constructor del Controlador aquellos servicios necesarios para su funcionamiento.

En el caso que nos ocupa, inyectaremos su clase cliente principal y el servicio de decodificación del token JWT de acceso. El código para el Controlador quedaría de la siguiente manera:

    public class LoginController : Controller
    {
        private readonly WebApiClient.ILoginClient _loginClient;
        private readonly IJwtTokenService _jwtTokenService;

        // OBTENEMOS MEDIANTE INYECCIÓN DE DEPENDENCIAS LA INSTANCIA
        // DE LoginClient y JwtTokenService A TRAVÉS DEL CONSTRUCTOR.
        public LoginController(WebApiClient.ILoginClient loginClient,
                                            IJwtTokenService jwtTokenService)
        {
            _loginClient = loginClient;
            _jwtTokenService = jwtTokenService;                        
        }
     
        // ...

     }

Sobrescribiendo el método OnActionExecuting(...)

El método OnActionExecuting(...) de la clase Controller, se ejecuta siempre después del constructor de la clase, y antes de la llamada a cualquier Acción del Controlador. 

El objetivo de utilizar este método, estriba en poder recuperar el contexto de la aplicación (HttpContext) para poder leer la cookie donde se almacena el token JWT (esto lo veremos a continuación), y extraer la información del usuario mediante el servicio de decodificación JwtTokenService.

Nota: También es posible acceder al HttpContext de la aplicación desde cualquier Acción del Controlador, pero lo haremos desde el OnActionExecuting(...) para no repetir el código de acceso y lectura de la cookie en todas las Acciones.

Importante: Tener en cuenta que no podemos acceder al HttpContext de la aplicación desde el Constructor de la clase Controller, nos devolverá Null y lanzará la correspondiente excepción.

El código completo del Controlador LoginController.cs sería el siguiente:

    public class LoginController : Controller
    {
        private readonly WebApiClient.ILoginClient _loginClient;
        private readonly IJwtTokenService _jwtTokenService;
        private string _tokenJWT;
        private UsuarioInfo _usuarioInfo;

        // OBTENEMOS MEDIANTE INYECCIÓN DE DEPENDENCIAS LA INSTANCIA
        // DE LoginClient y JwtTokenService A TRAVÉS DEL CONSTRUCTOR.
        public LoginController(WebApiClient.ILoginClient loginClient,
                                            IJwtTokenService jwtTokenService)
        {
            _loginClient = loginClient;
            _jwtTokenService = jwtTokenService;                        
        }

        // SOBRESCRIBIMOS EL MÉTODO OnActionExecuting.
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            // OBTENEMOS EL TOKEN DE ACCESO DE LA COOKIE.
            _tokenJWT = context.HttpContext.Request.Cookies["_WebApiToken"];

            // DECODIFICAMOS EL TOKEN DE ACCESO, PARA OBTENER 
            // LOS DATOS DEL USUARIO AUTENTICADO.
            if (_tokenJWT != null)
            {
                _usuarioInfo = _jwtTokenService.DecodeJwtToken(_tokenJWT);
                // ALMACENAMOS LA INFORMACIÓN DEL USUARIO EN EL ViewData.
                ViewData["usuarioInfo"] = _usuarioInfo;
            }

            base.OnActionExecuting(context);
        }

        [HttpGet]
        public IActionResult Index(string error)
        {
            ViewData["error"] = error;
            return View();
        }

        [HttpPost]
        public IActionResult Index(WebApiClient.UsuarioLogin usuarioLogin)
        {            
            try
            {
                // OBTENEMOS EL OBJETO QUE CONTIENE EL TOKEN DE ACCESO DESDE
                // EL MÉTODO LoginAsync() DEL CLIENTE DE WEB API loginClient, 
                // PASÁNDOLE LAS CREDENCIALES DEL USUARIO usuarioLogin.            
                var loginResult = _loginClient.LoginAsync(usuarioLogin).Result.ToString();

                // CREAMOS UN OBJETO ANÓNIMO QUE REPRESENTE EL RESULTADO 
                // DEVUELTO POR EL MÉTODO LoginAsync() -> loginResult.
                var objetoAnonimo = new { nombre = string.Empty, token = string.Empty };

                // DESERIALIZAMOS loginResult SEGÚN EL OBJETO ANÓNIMO objetoAnonimo,
                // Y OBTENEMOS EL TOKEN JWT DE ACCESO.
                var tokenJWT = JsonConvert.DeserializeAnonymousType(loginResult, objetoAnonimo).token;

                // ALMACENAMOS EL TOKEN OBTENIDO EN UNA COOKIE.
                CookieOptions cookieOptions = new CookieOptions();
                // LE DAMOS 24 HORAS DE VIDA.
                cookieOptions.Expires = DateTime.Now.AddHours(24);
                cookieOptions.HttpOnly = true; // ??
                cookieOptions.Secure = true; // ??
                Response.Cookies.Append("_WebApiToken", tokenJWT, cookieOptions);

                // REDIRIGIMOS A LA ACCIÓN Index DEL CONTROLADOR PaisController
                return RedirectToAction("Index", "Pais");
            }
            catch (Exception ex)
            {
                // PARA CUALQUIER EXCEPCIÓN.
                ViewData["error"] = ex.Message;
                return View();
            }
        }
    }

Como vemos en el código, el funcionamiento está bastante claro y bien explicado. En la Acción Index del Controlador, recibiremos desde la Vista un objeto del tipo usuarioLogin con las credenciales de acceso del usuario, para poder obtener el token JWT de acceso.

Una vez obtenido el token, lo almacenaremos en una cookie para su posterior uso. En el método OnActionExecuting(...) leeremos la cookie (en el caso de que exista) para extraer la información del usuario, y enviaremos esta información a la Vista a través del ViewData[...].

El Controlador PaisController

Este será el Controlador principal donde realizaremos las acciones CRUD a través del cliente WebApiClient

La estructura básica del Controlador será la misma que vimos anteriormente, o sea, la inyección de dependencias a través del constructor y la utilización del método OnActionExecuting(...) para recuperar el token JWT de acceso almacenado en la cookie.

El código completo del controlador PaisController.cs sería el siguiente:

    public class PaisController : Controller
    {
        private readonly WebApiClient.IPaisClient _paisClient;
        private readonly IJwtTokenService _jwtTokenService;
        private string _tokenJWT;
        private UsuarioInfo _usuarioInfo;

        // OBTENEMOS MEDIANTE INYECCIÓN DE DEPENDENCIAS LA INSTANCIA
        // DE LoginClient y JwtTokenService A TRAVÉS DEL CONSTRUCTOR.
        public PaisController(WebApiClient.IPaisClient paisClient, 
                                           IJwtTokenService jwtTokenService)
        {
            _paisClient = paisClient;
            _jwtTokenService = jwtTokenService;
        }

        // SOBRESCRIBIMOS EL MÉTODO OnActionExecuting.
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            // OBTENEMOS EL TOKEN DE ACCESO DE LA COOKIE.
            _tokenJWT = context.HttpContext.Request.Cookies["_WebApiToken"];

            // DECODIFICAMOS EL TOKEN DE ACCESO, PARA OBTENER 
            // LOS DATOS DEL USUARIO AUTENTICADO.
            if (_tokenJWT != null)
            {
                _usuarioInfo = _jwtTokenService.DecodeJwtToken(_tokenJWT);
                // ALMACENAMOS LA INFORMACIÓN DEL USUARIO EN EL ViewData.
                ViewData["usuarioInfo"] = _usuarioInfo;
            }

            base.OnActionExecuting(context);
        }
       
        // GET: Pais
        public ActionResult Index()
        {
            try
            {
                // ASIGNAMOS EL TOKEN DE ACCESO A LA PROPIEDAD BearerTokenJWT.
                _paisClient.BearerTokenJWT = _tokenJWT;
                // RECUPERAMOS LOS DATOS.
                var _result = _paisClient.GetPaisAllAsync().Result;
                // Y LOS PASAMOS A LA VISTA.
                return View(_result);
            }
            catch (Exception ex)
            {
                // EVALÚA LA EXCEPCIÓN POR SI HA OCURRIDO
                // UN StatusCode 401 Unauthorized.
                return CompruebaTokenJWTValido(ex);
            }
        }

        // GET: Pais/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Pais/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(WebApiClient.Pais pais)
        {            
            try
            {
                // ASIGNAMOS EL TOKEN DE ACCESO A LA PROPIEDAD BearerTokenJWT.
                _paisClient.BearerTokenJWT = _tokenJWT;
                // CREAMOS EL OBJETO.
                var _result = _paisClient.PostPaisAsync(pais).Result;
                // REDIRIGIMOS A LA ACCIÓN INDEX.
                return RedirectToAction(nameof(Index));
            }
            catch (Exception ex)
            {
                // EVALÚA LA EXCEPCIÓN POR SI HA OCURRIDO
                // UN StatusCode 401 Unauthorized.
                return CompruebaTokenJWTValido(ex);
            }
        }

        // GET: Pais/Delete/5
        [HttpGet]
        public ActionResult Delete(Guid id)
        {
            try
            {
                // ASIGNAMOS EL TOKEN DE ACCESO A LA PROPIEDAD BearerTokenJWT.
                _paisClient.BearerTokenJWT = _tokenJWT;
                // OBTENEMOS EL OBJETO A BORRAR.
                var _pais = _paisClient.GetPaisAsync(id).Result;
                // LO PASAMOS A LA VISTA.
                return View(_pais);
            }
            catch (Exception ex)
            {
                // EVALÚA LA EXCEPCIÓN POR SI HA OCURRIDO
                // UN StatusCode 401 Unauthorized.
                return CompruebaTokenJWTValido(ex);
            }            
        }

        // POST: Pais/Delete/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Delete(Guid id, IFormCollection collection)
        {
            try
            {
                // ASIGNAMOS EL TOKEN DE ACCESO A LA PROPIEDAD BearerTokenJWT.
                _paisClient.BearerTokenJWT = _tokenJWT;
                // ELIMINAMOS EL OBJETO.
                var _result = _paisClient.DeletePaisAsync(id).Result;
                // REDIRIGIMOS A LA ACCIÓN INDEX.
                return RedirectToAction(nameof(Index));
            }
            catch (Exception ex)
            {
                // EVALÚA LA EXCEPCIÓN POR SI HA OCURRIDO
                // UN StatusCode 401 Unauthorized.
                return CompruebaTokenJWTValido(ex);
            }
        }

        // GET: Pais/Details/5
        public ActionResult Details(Guid id)
        {
            try
            {
                // ASIGNAMOS EL TOKEN DE ACCESO A LA PROPIEDAD BearerTokenJWT.
                _paisClient.BearerTokenJWT = _tokenJWT;
                // OBTENEMOS EL OBJETO.
                var _pais = _paisClient.GetPaisAsync(id).Result;
                // LO PASAMOS A LA VISTA.
                return View(_pais);
            }
            catch (Exception ex)
            {
                // EVALÚA LA EXCEPCIÓN POR SI HA OCURRIDO
                // UN StatusCode 401 Unauthorized.
                return CompruebaTokenJWTValido(ex);
            }
        }

        // GET: Pais/Edit/5
        public ActionResult Edit(Guid id)
        {
            try
            {
                // ASIGNAMOS EL TOKEN DE ACCESO A LA PROPIEDAD BearerTokenJWT.
                _paisClient.BearerTokenJWT = _tokenJWT;
                // OBTENEMOS EL OBJETO.
                var _pais = _paisClient.GetPaisAsync(id).Result;
                // LO PASAMOS A LA VISTA.
                return View(_pais);
            }
            catch (Exception ex)
            {
                // EVALÚA LA EXCEPCIÓN POR SI HA OCURRIDO
                // UN StatusCode 401 Unauthorized.
                return CompruebaTokenJWTValido(ex);
            }
        }

        // POST: Pais/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(Guid id, WebApiClient.Pais pais)
        {
            try
            {
                // ASIGNAMOS EL TOKEN DE ACCESO A LA PROPIEDAD BearerTokenJWT.
                _paisClient.BearerTokenJWT = _tokenJWT;
                // MODIFICAMOS EL OBJETO.
                _paisClient.PutPaisAsync(id, pais);
                // REDIRIGIMOS A LA ACCIÓN INDEX.
                return RedirectToAction(nameof(Index));
            }
            catch (Exception ex)
            {
                // EVALÚA LA EXCEPCIÓN POR SI HA OCURRIDO
                // UN StatusCode 401 Unauthorized.
                return CompruebaTokenJWTValido(ex);
            }
        }

        ////////////////////////
        /// MÉTODOS PRIVADOS ///
        ////////////////////////
        private ActionResult CompruebaTokenJWTValido(Exception ex)
        {
            // SI HA OCURRIDO UNA EXCEPCIÓN DEL TIPO ApiException
            // COMPROBAMOS SI EL StatusCode ES 401 Unauthorized Y
            // REDIRIGIMOS A LA PÁGINA DE LOGIN.
            if (ex.InnerException is WebApiClient.ApiException)
            {
                var statusCode = ((WebApiClient.ApiException)ex.InnerException).StatusCode;
                if (statusCode == StatusCodes.Status401Unauthorized)
                {
                    // REDIRIGIMOS A LA ACCIÓN DE LOGIN 
                    // CON EL TEXTO DEL ERROR COMO PARÁMETRO DE RUTA.
                    return RedirectToAction("Index", "Login", new { error = ex.InnerException.Message });
                }
            }
            // PARA CUALQUIER OTRA EXCEPCIÓN.
            ViewData["error"] = ex.Message;
            return View();
        }
    }

Importante: Como podemos ver en el código, lo más importante a destacar, es la forma en que el cliente WebApiClient trata las respuestas (Http Status Codes) devueltas por el Web API. Siempre que la respuesta del Web API sea diferente a los resultados devueltos esperados, el cliente WebApiClient lanzará una Excepción que nosotros deberemos capturar y tratar. Es por esto que el código de todas las acciones se encuentra envuelto en un Try / Catch.

 

Las Vistas

En este punto del desarrollo y para finalizar, ya podemos crear las Vistas de nuestra aplicación ASP.NET Core MVC. Pero antes, crearemos la Vista parcial _UsuarioInfoPartial.cshtml, la cual insertaremos posteriormente en todas y cada una de las Vistas de la aplicación.

La Vista _UsuarioInfoPartial.cshtml nos mostrará la información del usuario actual obtenida a partir del token JWT, así como su periodo de validez para acceder al Web API expresado en fecha y hora (desde / hasta).

El código sería el siguiente:

@model UsuarioInfo

@if(Model != null)
{
    <div class="alert alert-secondary row">
        <small>
            <b>@Model.Nombre @Model.Apellidos</b> |
            @Model.Rol | Válido desde <b>@Model.ValidoDesde.ToShortDateString() @Model.ValidoDesde.ToShortTimeString()</b>
                hasta <b>@Model.ValidoDesde.ToShortDateString() @Model.ValidoHasta.ToShortTimeString()</b>
        </small>
    </div>
}

Las Vistas para LoginController

El Controlador LoginController dispondrá de una única Vista Index.cshtml, la cual consistirá en un formulario de validación de usuarios mediante usuario y contraseña.

@model WebApiClient.UsuarioLogin
@{
    ViewData["Title"] = "Index";
}

<partial name="_UsuarioInfoPartial" model="@ViewData["usuarioInfo"]" />

<div class="row">
    <div class="col-md-3 form-horizontal mx-auto text-center">
        <h1>Login</h1>
        <form action="/Login/Index" method="post">
            <br />
            <input asp-for="Usuario" class="form-control" placeholder="Usuario" required autofocus />
            <span asp-validation-for="Usuario" class="text-danger"></span>
            <br />
            <input asp-for="Password" class="form-control" placeholder="Contraseña" type="password" required>
            <span asp-validation-for="Password" class="text-danger"></span>
            <br />
            <button class="btn btn-primary" type="submit">
                Iniciar sesión
            </button>
        </form>
    </div>
</div>

<br />
@if (ViewData["error"] != null)
{
    <div class="alert alert-danger text-center">@ViewData["error"]</div>
}

Como vemos en el código, en la parte superior se mostrará la información del usuario actual autenticado, y en la inferior, se nos indicará cualquier error o advertencia relacionada con el proceso de login. 

Nota: Estos "banners" informativos superior e inferior, serán comunes para todas las Vistas de la aplicación.

webapiclient-login

Las Vistas para PaisController

Por último, para el Controlador PaisController, definiremos una Vista para cada una de las Acciones CRUD que lo conforman. 

La Vista Index

@model IEnumerable<WebApiClient.Pais>
@{
    ViewData["Title"] = "Index";
}

<partial name="_UsuarioInfoPartial" model="@ViewData["usuarioInfo"]" />

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Nombre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Habitantes)
            </th>
            <th></th>
        </tr>
    </thead>

    <tbody>
        @if (Model != null)
        {
            @foreach (var item in Model)
            {
                <tr>
                    @*<td>
                        @Html.DisplayFor(modelItem => item.Id)
                    </td>*@
                    <td>
                        @Html.DisplayFor(modelItem => item.Nombre)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Habitantes)
                    </td>
                    <td>
                        @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
                        @Html.ActionLink("Details", "Details", new { id=item.Id }) |
                        @Html.ActionLink("Delete", "Delete", new { id=item.Id })
                    </td>
                </tr>
            }
        }
    </tbody>
</table>

@if (ViewData["error"] != null)
{
    <div class="alert alert-danger text-center">
        @ViewData["error"]
    </div>
}

webapiclient-index

La Vista Create

@model WebApiClient.Pais
@{
    ViewData["Title"] = "Create";
}

<partial name="_UsuarioInfoPartial" model="@ViewData["usuarioInfo"]" />

<h1>Create</h1>

<h4>Pais</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            @*NO ES NECESARIO INDICAR EL Id DEL OBJETO,
                YA QUE EL WEB API LO CREARÁ POR NOSOTROS*@
            @*<div class="form-group">
                    <label asp-for="Id" class="control-label"></label>
                    <input asp-for="Id" class="form-control" />
                    <span asp-validation-for="Id" class="text-danger"></span>
                </div>*@
            <div class="form-group">
                <label asp-for="Nombre" class="control-label"></label>
                <input asp-for="Nombre" class="form-control" />
                <span asp-validation-for="Nombre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Habitantes" class="control-label"></label>
                <input asp-for="Habitantes" class="form-control" />
                <span asp-validation-for="Habitantes" class="text-danger"></span>
            </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>

@if (ViewData["error"] != null)
{
    <div class="alert alert-danger text-center">
        @ViewData["error"]
    </div>
}

webapiclient-create

La Vista Details

@model WebApiClient.Pais
@{
    ViewData["Title"] = "Details";
}

<partial name="_UsuarioInfoPartial" model="@ViewData["usuarioInfo"]" />

<h1>Details</h1>

<div>
    <h4>Pais</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Id)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Id)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Nombre)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Nombre)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Habitantes)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Habitantes)
        </dd>
    </dl>
</div>
<div>
    @Html.ActionLink("Edit", "Edit", new { /* id = Model.PrimaryKey */ }) |
    <a asp-action="Index">Back to List</a>
</div>

@if (ViewData["error"] != null)
{
    <div class="alert alert-danger text-center">
        @ViewData["error"]
    </div>
}

webapiclient-details

La Vista Edit

@model WebApiClient.Pais
@{
    ViewData["Title"] = "Edit";
}

<partial name="_UsuarioInfoPartial" model="@ViewData["usuarioInfo"]" />

<h1>Edit</h1>

<h4>Pais</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            @*POR SUPUESTO, NO PODEMOS MODIFICAR
            EL Id DEL OBJETO*@ 
            @*<div class="form-group">
                    <label asp-for="Id" class="control-label"></label>
                    <input asp-for="Id" class="form-control" />
                    <span asp-validation-for="Id" class="text-danger"></span>
                </div>*@
            <div class="form-group">
                <label asp-for="Nombre" class="control-label"></label>
                <input asp-for="Nombre" class="form-control" />
                <span asp-validation-for="Nombre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Habitantes" class="control-label"></label>
                <input asp-for="Habitantes" class="form-control" />
                <span asp-validation-for="Habitantes" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

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

@if (ViewData["error"] != null)
{
    <div class="alert alert-danger text-center">
        @ViewData["error"]
    </div>
}

webapiclient-edit

La Vista Delete

@model WebApiClient.Pais
@{
    ViewData["Title"] = "Delete";
}

<partial name="_UsuarioInfoPartial" model="@ViewData["usuarioInfo"]" />

<h1>Delete</h1>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Pais</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Id)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Id)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Nombre)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Nombre)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Habitantes)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Habitantes)
        </dd>
    </dl>

    <form asp-action="Delete">
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-action="Index">Back to List</a>
    </form>
</div>

@if (ViewData["error"] != null)
{
    <div class="alert alert-danger text-center">
        @ViewData["error"]
    </div>
}

webapiclient-delete

 

   EtiquetasWeb API ASP.NET Core ASP.NET MVC C#

  Compartir


  Nuevo comentario

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

  Comentarios

Charles Charles

Hola Rafael:

Te agradezco las publicaciones que haces, me han servido para sacarme de dudas en la creación de paginas ASP.NET, para esta publicación en particular me queda la duda en Cual sería la URL para poder acceder a las vistas de este cliente, es decir, voy a escribir https://localhost:45345/Pages/Index ? o cual es la URL para acceder a todas las vistas del CRUD? gracias de antemano.
Rafael Acosta Administrador Rafael Acosta

@David:

No te preocupes por eso. Estas etiquetas solo indican a las Acciones, con que tipo de verbo Http (GET o POST) se puede acceder a las mismas. Por defecto, y si no indicas nada, se presupone que la petición será de tipo GET. 


Las etiquetas son de tipo restrictivo, o sea, si "decoras" en una Acción con [HttpPost] solo podrás acceder a ella mediante una petición POST (si es GET no  podrás).


Estas etiquetas se suelen utilizar cuando utilizas sobrecarga de métodos (Acciones). Por ejemplo el método Delete tiene dos Acciones, una del tipo [HttpGet] para acceder al registro que quieres borrar, y otra del tipo [HttpPost] para borrar efectivamente el registro de la BD.


David David

Hola de nuevo Rafael,

Gracias por la rápida respuesta, el problema ya está resuelto. Lo que sucedía era que el campo del modelo no es nullable ya que el campo correspondiente en la tabla de la base de datos (de MySQL) tampoco lo es, pero había algunos registros que tenían el valor por defecto para ese campo (0000-00-00 00:00:00) y eso era lo que estaba produciendo el error. Al actualizar todos los registros con un valor distinto, funciona perfectamente.

Aprovecho el comentario para plantearte otra pregunta, esta vez más concreta: en el controlador PaisController que aparece en este post (el de AspNetCoreClient), en todas las acciones que consumen una acción del API correspondiente a una petición HTTP Post aparece la etiqueta [HttpPost], sin embargo en las que corresponden a una acción HTTP Get del API algunas las tienen (por ejemplo, Pais/Delete) y otras no (por ejemplo, Pais/Edit). Mi pregunta es qué justifica que en el controlador de AspNetCoreClient algunas acciones tengan la cabecera y otras no.

Gracias de nuevo.
Rafael Acosta Administrador Rafael Acosta

@David:

Muchas gracias por tu comentario.


En principio, y con la información que das, es bastante difícil saber cual es exactamente el problema. Aún así, parece que estás recibiendo valores nulos en una variable del tipo DateTime que no los acepta. Prueba a definir los DateTime de tipo Nullable (DateTime?).


Para poder estudiar mejor el problema, te aconsejo formules la pregunta en STACK OVERFLOW EN ESPAÑOL. Allí podrás indicar el código y ampliar mejor el problema. Yo mismo o cualquier otro colaborador te responderemos.


 


David David

Hola Rafael,

En primer lugar me gustaría darte la enhorabuena por el magnífico blog que tienes, y también las gracias pues me está siendo muy útil en los últimos días.

He seguido los pasos de este post y el anterior para crear un cliente para un API. Estoy encontrando un problema, y es que una de las acciones de mi API devuelve un lista de objetos los cuales tienen un campo DateTime. Al consumir el API desde el proyecto de MVC, cuando el cliente del controlador correspondiente ejecuta la acción y me llevo el resultado a la vista correspondiente para hacer una tabla con los objetos me sale la excepción System.NullReferenceException en el Model de la vista. Haciendo la trazabilidad parece que el problema viene del campo DateTime. ¿Es problema del cliente? ¿Cómo podría solucionarse?

Muchas 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