Imaginad que tenemos un controlador MVC como el siguiente:
public class TestController : Controller
{
public IActionResult Add(int a, int b)
{
return Content($"Result: {a + b}");
}
}
Claramente, la acción Add()
retornará la suma de los enteros a
y b
que le llegarán como parámetros de la query string:
GET https://localhost:7182/test/add?a=1&b=2 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Result: 3
Pero, como sabemos, podríamos llamar a la acción sin indicar alguno de esos parámetros, o incluso ninguno de ellos:
Petición | Respuesta |
---|---|
GET /test/add?a=1&b=2 | Result: 3 |
GET /test/add?a=0&b=0 | Result: 0 |
GET /test/add?a=1 | Result: 1 |
GET /test/add | Result: 0 |
Esto es así porque el binder será capaz de poblar correctamente los parámetros a
y b
cuando estén presentes en la cadena de la petición y sean correctos, o les asignará su valor por defecto (0
) cuando no hayan sido suministrados.
Pero dado que el cero es un valor de entrada válido, a priori desde nuestro código no tendríamos forma de distinguir cuándo el parámetro ha sido omitido y cuándo se ha establecido expresamente.
¿Cómo podríamos hacerlo?
Publicado por José M. Aguilar a las 8:05 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, trucos
A veces, sobre todo en aplicaciones muy grandes, con las definiciones de rutas muy complejas o cuando nos toca analizar aplicaciones ajenas, puede ser interesante saber qué punto del código está procesando una petición determinada, ya sea un endpoint definido usando Endpoint Routing o Minimal APIs o bien sea una acción de un controlador MVC.
En este post vamos a ver cómo conseguirlo de forma muy sencilla, mediante la implementación un pequeño middleware que, insertado en el pipeline, añadirá en el encabezado información sobre el handler que generó la respuesta de la petición actual.

Dada una petición como la siguiente:
POST https://localhost:5001/friends HTTP/1.1
Host: localhost:5001
Content-Type: application/json
{ "name": "Jon", "age": 24 }
Lo habitual es que queramos recibir los datos ya materializados en forma de objeto de .NET. Esto podemos conseguirlo fácilmente desde una acción como la siguiente:
[Route("friends")]
public class FriendsController : Controller
{
[HttpPost]
public ActionResult Test([FromBody]Friend friend)
{
return Content($"Hello, {friend.Name}, you are {friend.Age} years old");
}
}
Pero aunque no es algo que ocurra con frecuencia, a veces podríamos necesitar recibir el cuerpo JSON de una petición como string
, es decir, en crudo, sin deserializarlo ni procesarlo de ninguna manera para, ya más adelante, hacerlo nosotros manualmente. Y aunque a primera vista pudiera resultar trivial, tiene un poco más de truco de lo que parece...
ConfigureServices()
de la clase Startup
con una línea como la siguiente:public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
...
}
Esto era todo lo que necesitábamos para poder utilizar cualquiera de estas tecnologías en nuestra aplicación. Sin embargo, a partir de ASP.NET Core 3.0, nuestro intellisense se vio inundado de opciones adicionales como AddControllers()
, AddRazorPages()
o AddControllersWithViews()
.¿Para qué sirven, y por qué ha sido necesario introducir estos nuevos extensores?

Hoy vamos a hablar de una de eso pequeños descubrimientos: el rutado dinámico de controladores. Introducido en ASP.NET Core 3, se trata de una característica que no ha sido especialmente publicitada ni comentada, pero puede ser de utilidad en algunas ocasiones, pues permite interferir en la forma de interpretar las rutas de las peticiones entrantes con objeto de decidir en cada caso cómo deben ser procesadas.


En este post vamos a ver un par de formas de sacar provecho de esta descripción:
- En primer lugar, usaremos Swagger UI para generar un sitio web interactivo de documentación y pruebas de nuestra API.
- Después, veremos cómo generar desde Visual Studio código cliente para acceder a la API.
Publicado por José M. Aguilar a las 8:05 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, openapi, swagger, webapi

El objetivo de OpenAPI es conseguir una fórmula normalizada para describir las capacidades de un servicio REST, independientemente de los lenguajes o tecnologías con las que sea implementado. Esta descripción, normalmente especificada en formato JSON o YAML, permite a clientes (sean humanos o máquinas) descubrir servicios, comprender sus capacidades y conocer sus detalles de funcionamiento sin necesidad de tener acceso a su código fuente o documentación textual.
Esta especificación formal abre interesantes posibilidades, pues a partir de la definición estandarizada de servicios es posible, entre otros,
- disponer de herramientas de diseño y modelado de servicios,
- generar automáticamente páginas de documentación,
- generar automáticamente código cliente y servidor para dichos servicios para distintos, frameworks y lenguajes de programación,
- generar automáticamente código de testing y validaciones,
- o generar automáticamente mocks de servicios.
Publicado por José M. Aguilar a las 8:05 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, openapi, swagger, webapi
Sin embargo, es menos conocido el hecho de que estas bibliotecas pueden incluir también recursos estáticos como imágenes, hojas de estilo o scripts, lo que resulta bastante interesante a la hora de crear componentes totalmente autosuficientes y muy reutilizables.
En este post vamos a ver cómo crear una RCL redistribuible que definirá el tag helper
<mario>
, cuya inclusión en una página hará que ésta muestre el conocido personaje correteando por la pantalla, como se muestra en la siguiente captura:
Como recordaréis, esta directiva era bastante útil para simplificar el código de las vistas y mejorar su legibilidad, pues permitía crear funciones reutilizables que mezclaban HTML y código de servidor, como en el siguiente ejemplo:
@* File: Test.cshtml *
@Multiplication(2)
@Multiplication(3)
@helper Multiplication(int x)
{
<h2>Multiplication table of @x</h2>
<ul>
@for (var i = 1; i <= 10; i++)
{
<li>@x * @i = @(x * i)</li>
}
</ul>
}
Hasta la versión 2.2, teníamos que conformarnos con apaños como los descritos en aquél post si queríamos emular lo que llevábamos tantos años utilizando con MVC 5 y anteriores. Y también entonces comentamos que había ciertas posibilidades de que en algún momento volviera a la vida, y éstas se han materializado, por fin, en ASP.NET Core 3.0.Aunque quizás más bien habría que hablar de reencarnación...

SetCompatibilityVersion()
que veíamos en la plantilla de proyectos ASP.NET Core MVC y Razor Pages desde la versión 2.1:public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Esta llamada era incluida de serie en los nuevos proyectos ASP.NET Core desde la versión 2.1, pero en la versión 3.0 ya no aparece. Y probablemente también os llame la atención a quienes ya habéis trabajado con ASP.NET Core 2.x, así que he pensado que sería interesante comentarlo por aquí.
Bueno, la cuestión es que en ASP.NET Core 3.0 ha vuelto a cambiar, y esperemos que sea por última vez ;)
Veamos en qué han consistido estos cambios.

Seguramente estáis al tanto de que Microsoft acaba de anunciar la disponibilidad de .NET Core 3.0 y tecnologías relacionadas, incluyendo ASP.NET Core 3.0.
Pues bien, me complace anunciaros que, sólo un día más tarde, desde CampusMVP hemos lanzado el nuevo curso Desarrollo Web con ASP.NET Core 3 MVC.
Se trata de una gran revisión del curso de desarrollo con ASP.NET Core MVC que ha formado ya con éxito a cientos de desarrolladores de todo el mundo, en la que hemos introducido los cambios y novedades que han venido de la mano de la nueva versión del framework.
En este post intentaremos responder a las siguientes preguntas:
Publicado por José M. Aguilar a las 8:09 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, autobombo, curso

- .NET Core 3.0
- C# 8
- ASP.NET Core 3.0
- Blazor server-side
- Entity Framework Core 3.0
- Entity Framework 6.3 (sí, ¡compatible con .NET Core!)
- SignalR 3.0
- ML.NET
- Soporte WinForms y WPF para .NET Core 3
- Visual Studio 2019 16.3
- Simplificación del archivo de proyecto
.csproj
- Uso del host genérico
- Introducción del endpoint routing
- Mayor modularidad en el registro de servicios de MVC
- Nuevo serializador/deserializador JSON (bye bye, JSON.NET!)
- Compatibilidad exclusivamente con .NET Core (bye bye, target .NET Framework!)
- Limpieza de
Microsoft.AspNetCore.App
- Cambios en la compilación de vistas
- Soporte para gRPC
- Y, por supuesto, muchas otras mejoras...
Novedades de ASP.NET Core 3.0
¡No os lo perdáis!
Publicado en: www.variablenotfound.com.
Publicado por José M. Aguilar a las 8:55 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, noticias, novedades
El problema
En el escenario concreto que planteaba nuestro querido lector era que quería proporcionar, a los componentes que necesitaban acceder a datos en su aplicación, un acceso limitado al contexto de datos de Entity Framework.Para ello, por un lado definía una interfaz parecida a la siguiente, que proporcionaba acceso a los repositorios:
public interface IUserRepositories
{
DbSet<User> Users { get; }
...
}
Por otra parte, definía otra interfaz que era la que permitía comprometer cambios (adiciones, supresiones, modificaciones) realizadas en el contexto de datos:public interface IUnitOfWork
{
int SaveChanges();
}
La idea de esta separación es que si un componente necesitaba exclusivamente consultar datos relativos a usuarios, sólo recibiría mediante inyección de dependencias la instancia de IUserRepositories
, mientras que si necesitaba persistir datos, recibiría adicionalmente un objeto IUnitOfWork
, por ejemplo:public class UserServices
{
...
public UserServices(IMapper mapper, IUserRepositories userRepos, IUnitOfWork uow)
{
_mapper = mapper;
_userRepos = userRepos;
_uow = uow;
}
public async Task Update(int id, UserDto user)
{
var user = await _userRepos.Users.FirstOrDefaultAsync(u=>u.Id == id);
if(user == null)
throw new Exception();
_mapper.Map(userDto, user);
await _uof.SaveChangesAsync();
}
}
Bien, pues el problema lo tenía precisamente a la hora de registrar las dependencias de forma apropiada.
Entre otras, el tag helper
<cache>
es especialmente útil para almacenar en la memoria del servidor el resultado de procesar una porción de página o vista, de forma que pueda ser reutilizada en peticiones siguientes. Un ejemplo bastante básico, pero que deja bastante clara su utilidad y forma de uso, podría ser el siguiente:<h1>Atomic clock</h1>
<cache expires-after="@TimeSpan.FromSeconds(30)">
@{
await Task.Delay(3000);
}
Current time: @DateTime.UtcNow.ToLongTimeString()
</cache>
Al acceder a una vista con el código anterior por primera vez, se producirá un retardo forzado de tres segundos, se renderizará el interior del tag <cache>
mostrando la hora actual, y el resultado será almacenado en memoria durante 30 segundos.Si volvemos a acceder a la misma vista durante esos 30 segundos, el resultado será mostrado inmediatamente (sin esperar los tres segundos) y el cliente recibirá el contenido que se cacheó durante la primera visita. Al transcurrir este plazo, en la siguiente petición se volverá a procesar el contenido del tag helper, ejecutándose la espera y volviendo a generar y cachear el resultado enviado al cliente.
Pero podemos tener ejemplos algo más complejos, como el siguiente. En el interior del tag helper hemos insertado un view component que podría mostrar las últimas noticias obtenidas de una base de datos, en lo que podría ser la página de inicio de un servicio de información on-line:
<section>
<h1>Latest news</h1>
<cache expires-after="@TimeSpan.FromSeconds(30)">
<vc:latest-news></vc:latest-news>
<p>
Updated at: @DateTime.UtcNow.ToLongTimeString()
</p>
</cache>
</section>
Fijaos que en este caso el tiempo de invalidación del contenido almacenado en caché no tendría sentido que fuera periódico: ¿para qué invalidar la caché y volver a obtener las noticias de la base de datos cada X segundos si quizás no se ha introducido ninguna nueva desde la última vez que se renderizó la página? ¿No sería mejor recargar la caché sólo cuando se hayan modificado las noticias?En este post vamos a ver cómo conseguirlo, y un caso práctico de uso de esta técnica en un escenario muy frecuente.
Publicado por José M. Aguilar a las 8:30 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, patrones, trucos
Como recordaréis, se trata de una mejora en el módulo ASP.NET Core (ANCM) que hace que las aplicaciones se ejecuten directamente dentro del worker del servidor web (w3wp.exe), evitando las llamadas internas producidas cuando IIS actuaba como un mero proxy inverso.
Por verlo gráficamente, el siguiente diagrama muestra la arquitectura tradicional de un sistema ASP.NET Core funcionando sobre IIS. En el modelo out-of-process utilizado hasta ASP.NET Core 2.1, cada petición realizada desde el exterior era capturada por IIS, que a su vez lanzaba una petición HTTP local con los mismos contenidos hacia Kestrel, que era quien la procesaba ejecutando nuestro código. La respuesta generada desde Kestrel era enviada de vuelta a IIS como respuesta a la petición, quien la retornaba al agente de usuario:

En este modelo de funcionamiento, por tanto, cada petición HTTP entrante generaba otra petición HTTP interna que, aunque estuviera dentro de la misma máquina, imponía una penalización importante en el rendimiento de las aplicaciones.
El hosting in-process, aparecido con ASP.NET Core 2.2, cambia las reglas del juego eliminando esas peticiones HTTP internas y los retardos que implicaban, lo que ha posibilitado el espectacular incremento en el rendimiento que su uso ofrece. Ahora, el módulo para IIS ejecuta internamente la aplicación y se comunica con ella de forma directa:

Este es el modo por defecto de los nuevos proyectos ASP.NET Core creados usando las plantillas estándar, algo que podemos claramente ver si echamos un vistazo al archivo
.csproj
de un proyecto recién creado, ya sea desde Visual Studio o desde .NET Core CLI: <PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
Si tenéis un proyecto ASP.NET Core 2.1 o anterior y lo actualizáis a 2.2, tendréis que añadir el elemento <AspNetCoreHostingModel>
de forma manual para usar este modelo de ejecución.
Sin embargo, estas mejoras espectaculares no son gratis del todo, y creo que es interesante conocer un poco mejor lo que implica asumir este nuevo modo de funcionamiento de nuestras aplicaciones y responder a algunas preguntas que podríamos hacernos por el camino._ViewImports.cshtml
y _ViewStart.cshtml
se emplean para introducir directivas y código de inicialización para todas las vistas o páginas que se encuentren por debajo en la estructura de carpetas del proyecto.Es decir, si el archivo
_ViewImports.cshtml
se encuentra en la carpeta /Views
, todas las vistas que hay por debajo heredarán las directivas que hayamos especificado en su interior, de la misma forma que /Pages/_ViewImports.cshtml
afectara a las páginas presentes en la carpeta /Pages
y descendientes.Pues bien, hace unos días, un alumno del curso de ASP.NET Core en CampusMVP (que, por cierto, ha sido recientemente actualizado a la versión 2.2 del framework) planteaba una duda sobre un escenario algo más complejo. Si una aplicación tenía vistas en la carpeta
/Views
, también tenía vistas en áreas (carpeta /Areas
), e incluso pudiera tener algunas páginas Razor en /Pages
, la pregunta era si existía algún mecanismo para hacer que todas las vistas o páginas definidas en dichas carpetas compartieran directivas (como using
o importaciones de tag helpers) sin tener que duplicar este código en sus respectivos _ViewImports.cshtml
para cada una de ellas.A priori pensé que quizás el planteamiento sería retocar ligeramente el motor de vistas, pero, tras estudiarlo un rato, vi que el tema era mucho más sencillo :)

Por ejemplo, echando un vistazo al siguiente controlador podemos ver claramente una violación del Principio de Responsabilidad Única (SRP) en un controlador que conoce demasiados detalles sobre la forma de proceder al registrar un pedido:
public class OrderController: Controller
{
private readonly IOrderServices _orderServices;
[...] // Other private members
public OrderController(IOrderServices orderServices, IUserServices userServices,
IMailingService mailingServices, ISmsServices smsServices,
IPdfGenerator pdfGenerator, IMapper mapper
)
{
_orderServices = orderServices;
_userServices = userServices;
[...] // Other assignments...
}
[HttpPost]
public Task<IActionResult> Submit(OrderViewModel orderViewModel)
{
if(!ModelState.IsValid)
{
return View(orderViewModel);
}
var orderDto = _mapper.Map<OrderDto>(orderViewModel);
var orderResult = await _orderServices.AddAsync(orderDto);
if(!orderResult.Success)
{
return RedirecToAction("OrderError", new { error = orderResult.Error }));
}
var userPreferences = await _userServices.GetNotificationPreferencesAsync(CurrentUserId);
var pdfUrl = await _pdfGenerator.GenerateOrderAsync(orderResult.Details);
if(userPreferences.NotificationMode == NotificationMode.Sms)
{
await _smsServices.NotifyNewOrderAsync(orderResult.Details, pdfUrl);
}
else
{
await _mailingServices.NotifyNewOrderAsync(orderResult.Details);
}
return RedirectToAction("ThankYou", new { id = orderResult.Details.OrderId } );
}
...
}
En dicho post comentaba también algunas cosas que podíamos hacer para solucionar el problema, así como una recomendación de lo que no debíamos hacer: disimular dependencias instanciando directamente componentes o utilizando otros "sabores" de inyección que hicieran menos evidente la relación de nuestra clase con otras de las que depende.Pues bien, como hoy estamos algo rebeldes, vamos a ver las técnicas que nos permitirían hacerlo cuando sea necesario o, dicho de otra forma, qué alternativas tenemos para usar dependencias desde los controladores sin que estas sean suministradas mediante inyección en su constructor.
Precaución: estas técnicas son consideradas antipatrones o, como mínimo, code smells en la mayoría de los escenarios, así que usadlas poco, con precaución y siempre con conocimiento de causa ;)
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.