Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 29 de marzo de 2016
ASP.NET Core
Una de las principales características del nuevo ASP.NET Core es su diseño modular. El framework no es ahora un mastodonte monolítico sino un gran conjunto de pequeños componentes que incluiremos en nuestras aplicaciones exclusivamente cuando sea necesario.

Esto, que a priori se muestra como una ventaja en términos de rendimiento y flexibilidad de nuestros sistemas, presenta también algunos pequeños inconvenientes que debemos tener en cuenta a la hora de desarrollar aplicaciones.

Por ejemplo, en versiones anterior de ASP.NET no teníamos que preocuparnos demasiado de la gestión de los archivos estáticos (JS, CSS, HTML, fuentes...) porque teníamos por abajo a IIS que se encargaba de procesar las peticiones a este tipo de recursos. Ahora, tras la llegada de ASP.NET Core, las aplicaciones que utilicen archivos estáticos deberán incluir expresamente código para procesarlos.

Pero antes de continuar, recordaros que aún estamos jugando con una release candidate de ASP.NET Core, por lo que parte de lo que veamos a continuación podría no ser totalmente cierto en unas semanas.

¿Error 404 en imágenes, archivos CSS y otros contenidos estáticos?

Muchas veces hemos revisado por aquí el concepto del pipeline y la forma en que se procesan las peticiones en ASP.NET Core, principalmente basada en la intervención de middlewares, que son los componentes que realmente incluyen la lógica para gestionar gestionar las peticiones y enviar resultados de vuelta al cliente.

Pipeline y middlewares
Pues bien, cuando creamos una aplicación ASP.NET Core partiendo desde cero, inicialmente no se introduce ningún middleware en el pipeline más allá de los adaptadores necesarios para que nuestra aplicación funcione sobre Kestrel, el servidor web del framework.

Así, dado que no hay middlewares que aporten su capacidad de proceso de peticiones, cualquier solicitud que hagamos a nuestra aplicación retornará irremediablemente un error 404 (not found). En este punto, no existe diferencia alguna entre las peticiones dirigidas a archivos estáticos o las dirigidas a frameworks de mayor nivel de abstracción como MVC o SignalR: simplemente no hay nadie capaz de procesarlas.

Observad que esto es diferente al esquema de funcionamiento que teníamos previamente con IIS y las versiones clásicas de ASP.NET. Ahí, las peticiones a recursos estáticos, presentes en disco, eran procesadas directamente por IIS, que tomaba el control y retornaba el resultado sin dejar lugar a que nuestra aplicación pudiera hacer algo al respecto.

Con ASP.NET Core, si queremos que desde nuestra aplicación se procesen peticiones a archivos estáticos debemos habilitarlo de forma expresa añadiendo un middleware con esta capacidad al pipeline. No habrá nadie por debajo, como en el caso de IIS, que lo vaya a hacer por nosotros.

En la práctica, esto tendremos que hacerlo en casi todas las aplicaciones, aunque hay algunos escenarios (por ejemplo, sistemas que ejerzan exclusivamente como proveedores de servicios o APIs a otras aplicaciones o sistemas cliente).

Introducing StaticFilesMiddleware

ASP.NET Core aporta de serie un middleware que, ubicado en el pipeline, vigilará las peticiones entrantes y tomará el control cuando la ruta del recurso solicitado coincida con un archivo estático existente en el sistema de archivos, retornando su contenido.

Este middleware, implementado en la clase StaticFilesMiddleware, se encuentra disponible en el paquete Nuget Microsoft.AspNetCore.StaticFiles, y debemos introducirlo en el pipeline desde el método Configure() de la clase Startup:
public void Configure(IApplicationBuilder app)
{
    ...
    app.UseStaticFiles();
    ...
}
StaticFilesMiddleware en el pipelineTras ello, una petición como "/test.html" retornaría el archivo con el mismo nombre ubicado en el raíz de la carpeta wwwroot del proyecto y lo mismo ocurriría con el resto de peticiones dirigidas a otros tipos de contenidos estáticos, como archivos de scripts u hojas de estilo. En estos casos, el periplo de la petición a través del pipeline finalizaría y se retornaría al cliente el contenido del archivo solicitado.

Sin embargo, si la ruta de una solicitud no coincidiera con la de un archivo estático existente, el middleware simplemente dejaría continuar la petición por el pipeline, de forma que middlewares posicionados posteriormente aún tendrían oportunidad de procesarla, retornándose un error 404 si finalmente no es gestionada por ningún componente.

El extensor UseStaticFiles() admite un parámetro de tipo StaticFileOptions donde podemos configurar algunos aspectos del comportamiento del middleware a la hora de servir archivos estáticos:

Propiedades de StaticFileOptions










Veamos un poco qué nos permiten configurar estos settings.

La propiedad ServeUnkwonFileTypes es un booleano que indica si se deben servir archivos con extensiones no reconocidas. Por defecto, es falso, lo que implica que peticiones del tipo /archivo.kkk retornarán un error 404 aunque el archivo exista físicamente; si establecemos el valor a true, su contenido será retornado y el content-type indicado al cliente será el especificado en la propiedad DefaultContentType.

Por otra parte, la propiedad RequestPath nos permite definir la ruta base desde la que se servirán los archivos estáticos, independientemente de su ubicación física en el sistema de archivos. Por ejemplo, si le establecemos el valor "/static", sólo se evaluará la existencia de archivos físicos cuando la URL comience por ese prefijo, y será ignorado a la hora de localizar el archivo. Así, aunque exista un archivo "index.html" en el raíz de wwwroot, una petición con el path "/index.html" retornaría un error 404, mientras que "/static/index.html" retornaría el contenido con éxito.

Encabezado customOnPrepareResponse permite introducir lógica justo antes de enviar la respuesta al cliente, donde tenemos oportunidad de retocar la respuesta a nuestro antojo. Por ejemplo, en el siguiente código añadimos un encabezado de copyright a las peticiones servidas por StaticFilesMiddleware:
app.UseStaticFiles(new StaticFileOptions()
{
    OnPrepareResponse = ctx =>
    {
        ctx.Context.Response.Headers
           .Add("X-Copyright", "Copyright 2016 - JMA");
    }
});
Mediante la propiedad  ContentTypeProvider podemos personalizar el mecanismo de obtención del content-type en función de la ruta de la petición. Esto podemos hacerlo creando una clase personalizada que implementa el interfaz  IContentTypeProvider, que básicamente impone la implementación de un método al que se le suministra la ruta de la solicitud, y debe retornar el content-type a asignarle.

Por último, la interesante propiedad FileProvider permite modificar el origen de los archivos estáticos. Por defecto, el middleware obtendrá del sistema de archivos del servidor, pero podríamos sustituirlo por otro, como un servicio de terceros como Azure Storage, una base de datos o el que queramos. En cualquier caso, se trata de una instancia de  IFileProvider, una abstracción sobre un sistema de archivos que proporciona funcionalidades de obtención de directorios y de archivos, así como de seguimiento de cambios sobre cualquier tipo de sistema de almacenamiento:
public interface IFileProvider
{
    IFileInfo GetFileInfo(string subpath);
    IDirectoryContents GetDirectoryContents(string subpath);
    IChangeToken Watch(string filter);
}

¡Y eso es todo por ahora! Creo que lo que hemos visto es suficiente para hacernos una idea básica de cómo funciona el proceso de archivos estáticos en ASP.NET Core, y ya en las siguientes entregas de continuaremos profundizando y estudiando más herramientas que tenemos a nuestro alcance.

Publicado en Variable Not Found.

2 Comentarios:

Javier Guerrero dijo...

Hola! Gracias por el artículo.
¿Como haces para "segurizar" el acceso a los archivos estáticos?

Saludos!

José María Aguilar dijo...

Hola!

El middleware de archivos estáticos no trae de serie ningún mecanismo para controlar la autorización en el acceso. Así al vuelo, al menos tendrías un par de opciones:

* Introducir justo antes de StaticFiles un middleware que compruebe si el usuario puede descargar o no los contenidos, dejando pasar la petición sólo en caso afirmativo.

* Crear un controlador/acción que sea el que realmente descargue los archivos, en lugar de StaticFilesMiddleware, puesto que ahí puedes usar el atributo [Authorize] sin problema. Por supuesto, estos archivos deberías ubicarlos fuera de "wwwroot" (de hecho, no te haría falta ni siquiera el StaticFilesMiddleware).

Saludos!