Cómo usar Rewrite URL entre recursos en ASP.NET Core Web API – I
Introducción
Lo que voy a explicar en esta entrada es válido para aplicaciones Web con ASP.NET Core, por lo que voy a hacer una aproximación con ASP.NET Core Web API.
La situación de partida es cubrir la necesidad de hacer una redirección de un recurso Web a otro.
En mi caso, tendré dos peticiones generales a una Web API a través de api/values y api/others.
La primera es la que aparecerá por defecto por Microsoft, y la segunda será prácticamente un copy/paste de la primera.
Por lo tanto, y llegados a este punto, la primera acción que llevaré a cabo será la de crear el proyecto de Web API.
Para lograrlo, abriré una ventana de comandos y escribiré el siguiente comando:
dotnet new webapi -o FooWebApi
Se creará un directorio de nombre FooWebApi con nuestro proyecto de tipo webapi.
La siguiente operación será abrir el proyecto con Visual Studio 2017, desplegar la carpeta Controllers del proyecto y copiar y pegar ValuesController.cs, y renombrar la clase copiada como OthersController.cs.
La única parte del código que cambiaré será la siguiente:
[HttpGet] public ActionResult<IEnumerable> Get() { return new string[] { $"{nameof(OthersController)} 1", $"{nameof(OthersController)} 2" }; }
Esta modificación es únicamente para diferenciar a values de others.
Compilaremos nuestro proyecto para comprobar que está en orden y lo ejecutaremos para comprobar en tiempo de ejecución que funciona tal y como esperamos.
Una vez comprobado su correcto funcionamiento, vamos a partir de la base de que cuando un cliente acceder al recurso api/others, éste no debería servir la petición, pero tampoco queremos que devuelva un 404, ya que vamos a suponer que nuestro recurso está en funcionamiento desde hace tiempo, muchos clientes lo consumen, y queremos que estos clientes estén informados de que ese recurso ya no va a estar operativo porque (es posible) que haya otro que le pudiera sustituir, pero sobre todo, no queremos enviar un 404 Not Found, sino un 410 Gone.
¿Cómo podemos hacer esto en ASP.NET Core?.
Existen muchas formas de hacerlo, y aquí veremos algunas de ellas.
Ninguna mejor o peor desde mi punto de vista.
Simplemente, formas o maneras diferentes en las que un programador puede hacer una acción como la que buscamos.
Pero empecemos hablando de la base para hacer este tipo de acciones.
Existe un middleware desarrollado por Microsoft llamado Rewrite dentro del namespace Microsoft.AspNetCore.Rewrite que nos permitirá reescribir acciones.
Este middleware suele usarse para hacer redirecciones a través del método AddRedirect.
Podríamos pensar en él para buscar un método que nos permitiera realizar un 410 Gone.
Pero un 410 Gone no es una redirección.
En Http Status Codes, los 3xx son Redirect, pero los 4xx son ClientError.
Si buscamos dentro de Microsoft.AspNetCore.Rewrite, no encontraremos un método AddClientError, así que lo primero que se nos ocurre es probar si AddRedirect nos podría servir.
Método 1. Uso de AddRedirect para un 4xx
Vamos a ponernos manos a la obra y abriremos el fichero Startup.cs de nuestra Web API.
Dentro de esta clase y dentro del método Configure añadiré la siguiente parte de código hacia el final.
var rewrite = new RewriteOptions() .AddRedirect("api/others", "", StatusCodes.Status410Gone); app.UseHttpsRedirection(); app.UseRewriter(rewrite); app.UseMvc();
Si ejecutamos nuestra aplicación Web, ésta dará un error en tiempo de ejecución:
System.ArgumentException: ‘replacement’
El problema aquí es que el parámetro replacement es una cadena de texto vacía.
En un 4xx, no hace falta hacer ninguna redirección, por lo que el parámetro replacement no nos serviría de nada, pero RedirectRule a través de AddRedirect la valida.
Para saltarnos esto, bastaría con modificar la siguiente parte de código con cualquier texto diferente de un texto vacío en replacement, como por ejemplo:
var rewrite = new RewriteOptions() .AddRedirect("api/others", ".", StatusCodes.Status410Gone); app.UseRewriter(rewrite);
Si ejecutamos ahora nuestra Web API y acudimos a api/others, ésta funcionará tal y como esperábamos devolviendo un 410.
También ocurrirá lo mismo si ponemos por ejemplo api/others/2.
Podríamos decir entonces que hemos resuelto el problema, y la verdad es que podría servirnos.
Sin embargo, seamos sinceros.
Esta forma de hacer las cosas, aunque válida, no es la correcta.
El parámetro replacement no es necesario.
Y devolver un 4xx en un método AddRedirect no explica correctamente a la hora de leer el código lo que está haciendo.
Es más, cualquier programador podría venir otro día y preguntar a quién se le ocurrió hacer esto así, y con razón.
Así que seamos un poco más serios y estrictor y démosle una vuelta a este asunto a ver si encontramos alguna solución más adecuada.
Método 2. «Tocar» el middleware de Rewrite
Quizás esta sea la parte más friky de hacerlo, y la verdad es que tiene su parte molona.
El código fuente del middleware es abierto, así que si existe AddRedirect y no existe AddClientError, puede ser una buena idea la de hacer un PR y ofrecer esta modificación para la Comunidad.
¿Cómo hacerlo?.
Pues básicamente modificando en el repo del middleware el archivo baseline.netcore.json, añadiendo en la clase RewriteOptionsExtensions los métodos AddClientError correspondientes, y creando una clase nueva llamada ClientErrorRule que gestione los 4xx, igual prácticamente que la de AddRedirect pero eliminando de ella la propiedad Replacement, y variando ligeramente el método ApplyRule para que funcione de acuerdo a los que queremos.
No pongo aquí el código, pero es bastante simple hacerlo.
La llamada a este código sería algo así como:
var rewrite = new RewriteOptions() .AddClientError("api/others", StatusCodes.Status410Gone) app.UseRewriter(rewrite);
Como vemos, quedaría de una forma muy similar a la que veíamos con AddRedirect con el fin de guardar la máxima compatibilidad posible.
Aunque esto que comento lo he hecho, no he enviado la PR por la siguiente consideración.
Haciendo esto, recompilando el ensamblado y asignándolo a nuestro proyecto, compruebo que su funcionamiento es el esperado, sin embargo, hablando este tema con una de las personas de Microsoft que gestionan este módulo y pese a que esto que he comentado funciona, me dió un par de pistas de cómo llevarlo a cabo sin tener que llegar a esto, y es justo lo que veremos a continuación.
Así que llegados a este punto, la pregunta obligada es, ¿y entonces cómo podemos hacerlo?.
Eso es lo que veremos en la siguiente entrada, dónde explicaré dos métodos diferentes para poder hacerlo sin tener que llegar a ninguna de las acciones que he indicado hasta ahora.
Hasta entonces…
Happy Coding!