En todos los casos son revisiones pequeñas y que no rompen nada de lo anterior, pero en cada uno de estos productos se han introducido mejoras que vale la pena conocer, por lo que, antes que nada, os recomiendo que echéis un vistazo a los artículos anteriores, que son los anuncios oficiales.
En este post vamos a ver rápidamente las novedades más destacables de ASP.NET Core 2.2.
Publicado por José M. Aguilar a las 8:44 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, novedades
Por ello, en ASP.NET Core MVC, lo habitual es que implementemos nuestros controladores atendiendo a este principio, y para ello utilicemos la técnica de inyección de dependencias en el constructor:
public class InvoiceController: Controller
{
private readonly IInvoiceServices _invoiceServices;
private readonly IMapper _mapper;
private readonly ILogger<InvoiceController> _logger;
public InvoiceController(
IInvoiceServices invoiceServices,
ILogger<InvoiceController> logger,
IMapper mapper)
{
_invoiceServices = invoiceServices;
_logger = logger;
_mapper = mapper;
}
...
}
Nota: aunque en este post estemos hablando de controladores ASP.NET Core MVC, las ideas que vamos a comentar aquí son también aplicables a ASP.NET MVC "clásico" e incluso a otro tipo de frameworks que comparten los mismos conceptos.
Publicado por José M. Aguilar a las 8:55 a. m.
Etiquetas: aspnetcoremvc, aspnetmvc, buenas prácticas, patrones
En este artículo vamos a ver cómo aprovechar las ventajas de la precompilación, y al mismo tiempo mantener la flexibilidad que nos ofrece la compilación en tiempo de ejecución que tradicionalmente hemos disfrutado en proyectos ASP.NET y ASP.NET Core.
Hoy seguiremos profundizando en este tema, pero esta vez nos centraremos en modificar los textos por defecto de las anotaciones de datos y hacer que simplemente decorando una propiedad con un atributo como
Required
consigamos obtener mensajes de error localizados y a nuestro gusto, sin necesidad de establecer el ErrorMessage
de forma explícita y, en algunos casos, ni siquiera tener que indicar dicho atributo.namespace LocalizationDemo.ViewModels
{
public class PersonViewModel
{
[Required(ErrorMessage ="The name is required")]
public string Name { get; set; }
}
}
Incluso es bastante fácil hacer que este texto aparezca traducido atendiendo a la cultura del hilo actual. Para ello, simplemente debemos configurar los servicios de localización apropiadamente, e indicar en la propiedad ErrorMessage
la clave del mensaje de error en el archivo RESX asociado a la clase:// En Startup.cs:
public void ConfigureServices()
{
...
services.AddLocalization(opt=>opt.ResourcesPath="Resources");
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization()
.AddDataAnnotationsLocalization(); // Esta línea añade la localización
// de data annotations
}
// En el view model:
namespace LocalizationDemo.ViewModels
{
public class PersonViewModel
{
// El mensaje de error está definido
// en Resources/ViewModels.PersonViewModel.resx
[Required(ErrorMessage ="NameIsRequired")]
public string Name { get; set; }
}
}
Esto es así de fácil para las validaciones que declaramos de forma explícita en nuestro código, mediante atributos de anotaciones de datos. Sin embargo, existen otro tipo de validaciones que se realizan de forma implícita (por ejemplo, cuando en un campo entero introducimos un valor no numérico) y cuyos mensajes de error no son tan sencillos de establecer.Publicado por José M. Aguilar a las 9:15 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, localizacion, trucos
Sin embargo, a veces olvidamos que estas mismas técnicas podemos utilizarlas para simplificar código en la implementación de convenciones o funciones más ligadas al negocio o a funcionalidades de nuestra aplicación. Por ejemplo, ¿no estáis aburridos de escribir acciones como las siguientes?
public class FriendsController: Controller
{
public IActionResult View(int id)
{
var friend = _friendServices.GetById(id);
if(friend == null)
return NotFound();
... // Prepare and show "View" view
}
public IActionResult Edit(int id)
{
var friend = _friendServices.GetById(id);
if(friend == null)
return NotFound();
... // Prepare and show "Edit" view
}
public IActionResult Delete(int id)
{
var friend = _friendServices.GetById(id);
if(friend == null)
return NotFound();
... // Prepare and show "Delete" view
}
...
}
Pues bien, vamos a aplicar el mismo principio para simplificar este código y eliminar duplicidades extrayendo las porciones comunes a un filtro, resultando en algo así de bonito:
[Autoload(typeof(Friend))] // ¡Magia!
public class FriendsController: Controller
{
public Task<IActionResult> View(Friend friend)
{
... // Prepare and show "View" view
}
public Task<IActionResult> Edit(Friend friend)
{
... // Prepare and show "Edit" view
}
public Task<IActionResult> Delete(Friend friend)
{
... // Prepare and show "Delete" view
}
...
}
No sé lo útil que podrá resultar en la práctica pero, como mínimo, nos ayudará a conocer mejor cómo funciona por dentro el framework ASP.NET Core MVC.
Básicamente, la duda era la siguiente:
¿Hay alguna forma sencilla de añadir el atributo [Authorize]
, pero sólo a los controladores que se encuentren en una carpeta determinada del proyecto, sin tener que aplicarlo uno a uno?
La respuesta rápida es: sí. Bueno, con matices ;) Incluso hay varias opciones, así que vamos a ver algunas de ellas, y de paso repasamos conceptos de ASP.NET Core MVC :)ASP.NET Core 2.1 continúa profundizando en esa línea e incluye entre sus novedades el nuevo atributo
[ApiController]
, un decorador aplicable a controladores que los identifica como puntos de entrada de APIS, aplicando de forma automática una serie de convenciones bastante útiles a la hora de crear este tipo de componentes:[ApiController]
[Route("api/[controller]")]
public class ValuesController : ControllerBase
{
...
}
Fijaos que, a diferencia de ASP.NET Web API (.NET Framework), se ha optado por utilizar un atributo en lugar de emplear herencia (en aquél framework existía la clase ApiController
).
A continuación veremos qué debemos tener en cuenta a la hora de aplicar este atributo a nuestros controladores y qué convenciones son las que estaremos asumiendo al utilizarlo.
Publicado por José M. Aguilar a las 8:55 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, novedades, webapi
IActionResult
(o un Task<IActionResult>
en caso de ser asíncronas). Este interfaz, cuya única misión es definir el método de ejecución asíncrona del resultado, ExecuteResultAsync()
, es implementado por la gran variedad de tipos de resultado ofrecidos de serie por el framework, como ViewResult
, RedirectResult
o FileResult
.Sin embargo, no es esta la única opción disponible a la hora de especificar el resultado de una acción, como veremos a continuación.
Desde el punto de vista práctico, es una solución fantástica y muy cómoda de utilizar, y de hecho seguro que en más de una ocasión os habréis visto abusando de ella, es decir, habréis introducido en claims más información de la cuenta sólo por lo cómodo que resulta utilizarlo.
Pero claro, esto tiene su coste. Cada dato extra que añadimos a esos tokens supone transferencia adicional a la hora de recibir peticiones y retornar resultados, lo que puede repercutir en costes de operación si nuestro sistema tiene mucho tráfico, y en cualquier caso, aumentar los tiempos de respuesta de la aplicación.
Para solucionar estos problemas, ASP.NET Core incorpora el concepto de claims transformation, que son componentes ejecutados durante la fase de autenticación que toman el
ClaimsPrincipal
generado a partir de la información recibida en el token y permiten extenderlo o modificarlo para añadirle claims o atributos adicionales.De esta forma, podríamos introducir en los tokens que viajan por la red un único claim con el nombre o identificador único del usuario, y mediante una transformación posterior añadirle atributos adicionales, como su nombre completo, roles de seguridad y otros que nos hagan falta a nivel de aplicación.
Publicado por José M. Aguilar a las 8:55 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, autenticación, jwt, trucos
En las versiones "clásicas" de ASP.NET MVC era algo que se podía resolver con relativa facilidad gracias a proyectos como RazorGenerator o RazorEngine, pero, como en ASP.NET Core las cosas han cambiado bastante, he pensado que quizás sería interesante comentar por aquí cómo podríamos conseguirlo en este nuevo framework.
Aunque a muchos nos pueda parecer raro, aún hay bastantes empresas y desarrolladores trabajando en Visual Basic .NET. En ocasiones sólo es para mantener código antiguo, pero otras veces (más de lo que puede parecer) incluso para crear nuevos sistemas aprovechando la experiencia de los desarrolladores y bases de código existente.
Si estáis en esta situación y vais a comenzar a trabajar con ASP.NET Core, mi recomendación siempre es que aprovechéis el momento de cambios para dar el salto a C#. Spoiler: no os arrepentiréis ;)Bien, respecto a la pregunta motivo del post, en teoría debería poderse, y de hecho ya difundimos por aquí hace tiempo la noticia oficial de que sería soportado, pero la verdad es que desde entonces no he vuelto a oir hablar mucho del tema, ni he visto ejemplos, ni movimiento en la comunidad ni nada parecido, lo cual me hizo dudar de la situación actual de este tema.
En este post vamos a hacer un tour rápido para ver, en la práctica, cómo está el soporte para VB.NET en la versión actual de ASP.NET Core (2.0).
JSON Web Tokens, o JWT para los amigos, es sin duda una de las fórmulas más utilizadas para autenticación en servicios o APIs HTTP gracias a su sencillez de uso y a la seguridad que aportan en determinados escenarios frente a otras opciones como las populares cookies.
En este post vamos a ver cómo implementar funcionalidades básicas de generación de tokens JWT en ASP.NET Core MVC, y cómo asegurar nuestros APIs utilizándolos para autenticar a los usuarios.
Pero dado que siempre hablábamos de Visual Studio, es lógico preguntarse si posible conseguir exactamente lo mismo desde la línea de comandos o, llevándolo al extremo, en entornos no Windows como Linux o Mac, así que en este post veremos cómo conseguirlo.
Tras seleccionar el tipo de elemento a crear, aparecerá un segundo cuadro de diálogo solicitándonos información sobre el mismo. Por ejemplo, en la siguiente captura de pantalla se muestra el diálogo de creación de vistas MVC, donde debemos introducir el nombre de la vista, la plantilla a utilizar, la clase del Modelo para vistas tipadas, etc:
Después de cumplimentar estos datos, se generará automáticamente el código fuente del elemento indicado. Hasta aquí bien, pero, ¿qué ocurre si ese código generado no se ajusta exactamente a nuestras necesidades? O preguntándolo de otra forma, ¿es posible modificar las plantillas de generación de código utilizadas en estos casos para adaptarlas a nuestras preferencias?
Pues sí, y vamos a ver cómo :)
Resulta que varias veces he publicado un proyecto ASP.NET Core y, tras finalizar y probar un poco la aplicación, he visto que me había dejado por detrás alguna chorradilla en una vista que tenía que corregir rápidamente. En lugar de volver a publicar el proyecto completo, cuando me ocurre esto suelo a retocar la vista y actualizar sólo ese archivo en el servidor, por ejemplo desde el menú contextual del archivo en Visual Studio:
Pero en este caso, una vez finalizó la subida del archivo al servidor, pulsé F5 en el navegador para comprobar que ya estaba todo correcto y… ¡vaya, todo seguía igual que antes! No pasa nada, pensé que no había publicado bien, por lo que volví a repetir el proceso y pocos segundos después pude comprobar que los cambios seguían sin ser aplicados en el servidor. ¿Qué podía estar ocurriendo?
Ah, claro, ¡el caché! La vista era retornada por una acción MVC decorada con el filtro
ResponseCache
, por lo que podría ser normal que continuara llegando al navegador la versión anterior. Eliminé caché, incluso probé desde otro navegador y ¡todo seguía igual que antes!Ya lo único que se me ocurría es que la publicación hubiera fallado por algún siniestro bug de Visual Studio que no dejara rastro en las trazas, así que decidí ignorar al intermediario. Fui directamente al servidor para editar manualmente el archivo de la vista y… maldición, ¡no la encuentro! :-/
¿Qué está ocurriendo aquí?
https://www.variablenotfound.com/2018/05/implementando-mas-facilmente-background.html
Es relativamente frecuente encontrar aplicaciones en las que necesitamos disponer de un proceso en background ejecutándose de forma continua. Hace poco hablábamos de IApplicationLifetime, un servicio del framework que nos permitía introducir código personalizado al iniciar y detener las aplicaciones, y probablemente habréis pensado que éste sería un buen sitio para gestionar el inicio y finalización de estas tareas en segundo plano.
Y aunque es posible, ASP.NET Core proporciona otras fórmulas más apropiadas para conseguirlo: los hosted services. Mediante este mecanismo, podemos crear servicios que serán gestionados por el host, y que serán iniciados y finalizados automáticamente junto con la aplicación.
public async Task<IActionResult> GetTheAnswerToLifeUniverseAndEverything()
{
await Task.Delay(30000); // Simulando un proceso costoso...
return Content("42!");
}
Cuando nuestros usuarios pulsen dicho botón, necesariamente habrán de esperar varios segundos para obtener una respuesta. Pero como suelen ser Obviamente, esto no hace sino empeorar las cosas. El servidor, que ya estaba ocupado intentando responder la primera petición, no tiene ya que atender a una, sino a todas las que se han generado tras este ataque, cuando en realidad no tiene sentido: para tranquilizar al usuario basta con entregarle el resultado de una de ellas, por lo que todos los hilos sobrantes simplemente están malgastando recursos del servidor realizando operaciones para obtener resultados que nadie va a consumir.
Estaría bien poder cancelar esas peticiones largas si tenemos la seguridad de que ningún cliente está esperándolas, ¿verdad?
Otro ejemplo muy habitual lo encontramos con
MapPath()
, un método perteneciente a la clase HttpServerUtility
de ASP.NET "clásico" que utilizábamos para obtener una ruta física a partir de la ruta virtual o relativa de un recurso. Por ejemplo, en el siguiente código mostramos cómo averiguar la ruta en disco de una imagen utilizando este método:var path = HttpContext.Current.Server.MapPath("~/images/image.jpg"); // path = C:\inetpub\wwwroot\mysite\images\image.jpgPues bien, ni en ASP.NET Core ni en MVC tenemos disponible la clase
System.Web.HttpContext
, ni por tanto una propiedad Server
de tipo HttpServerUtility
que usábamos para invocar al método MapPath()
. Sin embargo, disponemos de herramientas alternativas que nos permiten conseguir lo mismo, aunque, eso sí, de forma algo menos directa.Por ejemplo, en el siguiente código vemos cómo podíamos aprovechar los eventos
Application_Start()
y Application_End()
para guardar un registro básico de estos sucesos:public class MvcApplication : System.Web.HttpApplication
{
private static string _logFile;
protected void Application_Start()
{
...
_logFile = Server.MapPath("log.txt");
File.AppendAllText(_logFile, DateTime.Now + ": Starting\n");
}
protected void Application_End()
{
File.AppendAllText(_logFile, DateTime.Now + ": Stopping\n");
}
}
Sabemos que en ASP.NET Core no existe Global.asax, por lo esta fórmula ya no está disponible. Sin embargo, el nuevo framework ofrece una alternativa bastante razonable mediante el interfaz IApplicationLifetime
, proporcionando, entre otras cosas, vías para suscribirnos a eventos relacionados con el ciclo de vida de una aplicación.