Archivos estáticos en ASP.NET Core

Por Rick Anderson y Kirk Larkin

Los archivos estáticos, como HTML, CSS, imágenes y JavaScript, son activos que una aplicación de ASP.NET Core proporciona directamente a los clientes de forma predeterminada.

Proporcionar archivos estáticos

Los archivos estáticos se almacenan en el directorio raíz web del proyecto. El directorio predeterminado es {content root}/wwwroot, pero se puede cambiar con el método UseWebRoot. Para más información, vea Raíz del contenido y Raíz web.

El método CreateBuilder establece la raíz de contenido en el directorio actual:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Se puede acceder a los archivos estáticos a través de una ruta de acceso relativa a la raíz web. Por ejemplo, las plantillas de proyecto Aplicación web contienen varias carpetas dentro de la carpeta wwwroot:

  • wwwroot
    • css
    • js
    • lib

Considere la posibilidad de crear la carpeta wwwroot/images y agregar el archivo wwwroot/images/MyImage.jpg. El formato del URI para acceder a un archivo en la carpeta images es https://<hostname>/images/<image_file_name>. Por ejemplo, https://localhost:5001/images/MyImage.jpg.

Suministro de archivos en la raíz web

Las plantillas de aplicación web predeterminadas llaman al método UseStaticFiles en Program.cs, que permite proporcionar archivos estáticos:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

La sobrecarga del método UseStaticFiles sin parámetros marca los archivos en la raíz web como que se pueden proporcionar. El siguiente marcado hace referencia a wwwroot/images/MyImage.jpg:

<img src="~/images/MyImage.jpg" class="img" alt="My image" />

En el marcado anterior, el carácter de tilde de la ñ ~ apunta a la raíz web.

Proporcionar archivos fuera de la raíz web

Considere una jerarquía de directorios en la que residen fuera de la raíz web los archivos estáticos que se van a proporcionar:

  • wwwroot
    • css
    • images
    • js
  • MyStaticFiles
    • images
      • red-rose.jpg

Una solicitud puede acceder al archivo red-rose.jpg configurando el middleware de archivos estáticos como se muestra a continuación:

using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
           Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
    RequestPath = "/StaticFiles"
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

En el código anterior, la jerarquía del directorio MyStaticFiles se expone públicamente a través del segmento de URI StaticFiles. Una solicitud a https://<hostname>/StaticFiles/images/red-rose.jpg sirve al archivo red-rose.jpg.

El siguiente marcado hace referencia a MyStaticFiles/images/red-rose.jpg:

<img src="~/StaticFiles/images/red-rose.jpg" class="img" alt="A red rose" />

Para entregar archivos desde varias ubicaciones, vea Entrega de archivos desde varias ubicaciones.

Establecer encabezados de respuesta HTTP

Se puede usar un objeto StaticFileOptions para establecer encabezados de respuesta HTTP. Además de configurar el servicio de archivos estáticos desde la raíz web, el código siguiente establece el encabezado Cache-Control:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

var cacheMaxAgeOneWeek = (60 * 60 * 24 * 7).ToString();
app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        ctx.Context.Response.Headers.Append(
             "Cache-Control", $"public, max-age={cacheMaxAgeOneWeek}");
    }
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

El código anterior hace que los archivos estáticos estén disponibles públicamente en la memoria caché local durante una semana (604800 segundos).

Autorización de archivos estáticos

Las plantillas de ASP.NET Core llaman a UseStaticFiles antes de llamar a UseAuthorization. La mayoría de las aplicaciones siguen este patrón. Cuando se llama al middleware de archivos estáticos antes de al middleware de autorización:

  • No se realizan comprobaciones de autorización en los archivos estáticos.
  • Los archivos estáticos que atiende el middleware de archivos estáticos, como los que están en wwwroot, son accesibles públicamente.

Para proporcionar archivos estáticos según su autorización:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using StaticFileAuth.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
           Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
    RequestPath = "/StaticFiles"
});

app.MapRazorPages();

app.Run();

En el código anterior, la directiva de autorización de reserva requiere que se autentiquen todos los usuarios. Los puntos de conexión como los controladores, Razor Pages, etc. que especifican sus propios requisitos de autorización no usan la directiva de autorización de reserva. Por ejemplo, Razor Pages, controladores o métodos de acción con [AllowAnonymous] o [Authorize(PolicyName="MyPolicy")] usan el atributo de autorización aplicado en lugar de la directiva de autorización de reserva.

RequireAuthenticatedUser agrega DenyAnonymousAuthorizationRequirement a la instancia actual, lo que exige que el usuario actual se autentique.

Los recursos estáticos en wwwroot son accesibles públicamente porque se llama al middleware de archivos estáticos predeterminado (app.UseStaticFiles();) antes de a UseAuthentication. Los recursos estáticos de la carpeta MyStaticFiles requieren autenticación. En el código de ejemplo se muestra esto.

Un enfoque alternativo a proporcionar archivos según la autorización es:

  • Almacénelos fuera de wwwroot y cualquier directorio al que el middleware de archivos estáticos tenga acceso.
  • Proporciónelos a través de un método de acción al que se aplica la autorización y devuelva un objeto FileResult:
[Authorize]
public class BannerImageModel : PageModel
{
    private readonly IWebHostEnvironment _env;

    public BannerImageModel(IWebHostEnvironment env) =>
        _env = env;

    public PhysicalFileResult OnGet()
    {
        var filePath = Path.Combine(
                _env.ContentRootPath, "MyStaticFiles", "images", "red-rose.jpg");

        return PhysicalFile(filePath, "image/jpeg");
    }
}

Examen de directorios

El examen de directorios permite enumerar directorios dentro de los directorios especificados.

Por motivos de seguridad, el examen de directorios está deshabilitado de forma predeterminada. Para obtener más información, consulte Consideraciones de seguridad para archivos estáticos.

Habilite el examen de directorios con AddDirectoryBrowser y UseDirectoryBrowser:

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDirectoryBrowser();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

var fileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.WebRootPath, "images"));
var requestPath = "/MyImages";

// Enable displaying browser links.
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = fileProvider,
    RequestPath = requestPath
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
    FileProvider = fileProvider,
    RequestPath = requestPath
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

El código anterior permite el examen de directorios de la carpeta wwwroot/images usando la dirección URL https://<hostname>/MyImages, con vínculos a cada archivo y carpeta:

examen de directorios

AddDirectoryBrowseragrega servicios requeridos por el middleware de examen de directorios, incluido HtmlEncoder. Estos servicios se pueden agregar mediante otras llamadas, como AddRazorPages, pero se recomienda llamar a AddDirectoryBrowser para asegurarse de que los servicios se agregan en todas las aplicaciones.

Suministro de documentos predeterminados

El establecimiento de una página predeterminada proporciona a los visitantes un punto inicial en un sitio. Para servir un archivo predeterminado desde wwwroot sin necesidad de que la dirección URL de la solicitud incluya el nombre del archivo, llame al método UseDefaultFiles:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseDefaultFiles();

app.UseStaticFiles();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Debe llamarse a UseDefaultFiles antes de a UseStaticFiles para proporcionar el archivo predeterminado. UseDefaultFiles es un sistema de reescritura de direcciones URL que no proporciona el archivo.

Con UseDefaultFiles, las solicitudes a una carpeta en wwwroot buscan:

  • default.htm
  • default.html
  • index.htm
  • index.html

El primer archivo que se encuentra en la lista se sirve como si la solicitud incluyera el nombre de archivo. La dirección URL del explorador sigue reflejando el URI solicitado.

El código siguiente cambia el nombre de archivo predeterminado a mydefault.html:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

var options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);

app.UseStaticFiles();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

UseFileServer para documentos predeterminados

UseFileServer combina la funcionalidad de UseStaticFiles y UseDefaultFiles y, opcionalmente, la de UseDirectoryBrowser.

Llame a app.UseFileServer para poder proporcionar archivos estáticos y el archivo predeterminado. El examen de directorios no está habilitado:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseFileServer();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

El código siguiente permite proporcionar archivos estáticos, el archivo predeterminado y el examen de directorios:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDirectoryBrowser();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseFileServer(enableDirectoryBrowsing: true);

app.UseRouting();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Tenga en cuenta la siguiente jerarquía de directorios:

  • wwwroot
    • css
    • images
    • js
  • MyStaticFiles
    • images
      • MyImage.jpg
    • default.html

El código siguiente permite proporcionar archivos estáticos, el archivo predeterminado y el examen de directorios de MyStaticFiles:

using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDirectoryBrowser();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseFileServer(new FileServerOptions
{
    FileProvider = new PhysicalFileProvider(
           Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
    RequestPath = "/StaticFiles",
    EnableDirectoryBrowsing = true
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Se debe llamar a AddDirectoryBrowser cuando el valor de la propiedad EnableDirectoryBrowsing es true.

Al usar la jerarquía de archivos y el código anteriores, las direcciones URL se resuelven como se indica a continuación:

Identificador URI Respuesta
https://<hostname>/StaticFiles/images/MyImage.jpg MyStaticFiles/images/MyImage.jpg
https://<hostname>/StaticFiles MyStaticFiles/default.html

Si no existe ningún archivo con el nombre predeterminado en el directorio MyStaticFiles, https://<hostname>/StaticFiles devuelve la lista de directorios con vínculos activos:

Lista de archivos estáticos

UseDefaultFiles y UseDirectoryBrowser realizan un redireccionamiento del lado cliente desde el URI de destino sin una / final hasta el URI de destino una / final. Por ejemplo, desde https://<hostname>/StaticFiles hasta https://<hostname>/StaticFiles/. Las direcciones URL relativas dentro del directorio StaticFiles no son válidas sin una barra diagonal final (/), a menos que se use la opción RedirectToAppendTrailingSlash de DefaultFilesOptions.

FileExtensionContentTypeProvider

La clase FileExtensionContentTypeProvider contiene una propiedad Mappings que actúa como una asignación de extensiones de archivo para tipos de contenido MIME. En el ejemplo siguiente, se asignan varias extensiones de archivo a los tipos MIME conocidos. Se reemplaza la extensión .rtf y se quita .mp4:

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

// Set up custom content types - associating file extension to MIME type
var provider = new FileExtensionContentTypeProvider();
// Add new mappings
provider.Mappings[".myapp"] = "application/x-msdownload";
provider.Mappings[".htm3"] = "text/html";
provider.Mappings[".image"] = "image/png";
// Replace an existing mapping
provider.Mappings[".rtf"] = "application/x-msdownload";
// Remove MP4 videos.
provider.Mappings.Remove(".mp4");

app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = provider
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Vea Tipos de contenido MIME.

Tipos de contenido no estándar

El middleware de archivos estáticos entiende casi 400 tipos de contenido de archivo conocidos. Si el usuario solicita un archivo con un tipo de archivo desconocido, el middleware de archivos estáticos pasa la solicitud al siguiente middleware de la canalización. Si ningún middleware se ocupa de la solicitud, se devuelve una respuesta 404 No encontrado. Si se habilita la exploración de directorios, se muestra un vínculo al archivo en una lista de directorios.

El código siguiente permite proporcionar tipos desconocidos y procesa el archivo desconocido como una imagen:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles(new StaticFileOptions
{
    ServeUnknownFileTypes = true,
    DefaultContentType = "image/png"
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Con el código anterior, una solicitud para un archivo con un tipo de contenido desconocido se devuelve como una imagen.

Advertencia

Habilitar ServeUnknownFileTypes supone un riesgo para la seguridad. Está deshabilitado de forma predeterminada y no se recomienda su uso. FileExtensionContentTypeProvider proporciona una alternativa más segura a ofrecer archivos con extensiones no estándar.

Entrega de archivos desde varias ubicaciones

Tenga en cuenta la página Razor siguiente que muestra el archivo /MyStaticFiles/image3.png:

@page

<p> Test /MyStaticFiles/image3.png</p>

<img src="~/image3.png" class="img" asp-append-version="true" alt="Test">

UseStaticFiles y UseFileServer tienen como valor predeterminado el proveedor de archivos que apunta a wwwroot. Se pueden proporcionar instancias adicionales de UseStaticFiles y UseFileServer con otros proveedores de archivos para proporcionar archivos desde otras ubicaciones. En el ejemplo siguiente se llama a UseStaticFiles dos veces para entregar archivos desde wwwroot y MyStaticFiles:

app.UseStaticFiles(); // Serve files from wwwroot
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"))
});

Con el código anterior:

El código siguiente actualiza WebRootFileProvider, que permite al asistente de etiquetas de imagen proporcionar una versión:

var webRootProvider = new PhysicalFileProvider(builder.Environment.WebRootPath);
var newPathProvider = new PhysicalFileProvider(
  Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"));

var compositeProvider = new CompositeFileProvider(webRootProvider,
                                                  newPathProvider);

// Update the default provider.
app.Environment.WebRootFileProvider = compositeProvider;

app.UseStaticFiles();

Consideraciones de seguridad para archivos estáticos

Advertencia

UseDirectoryBrowser y UseStaticFiles pueden producir pérdidas de información confidencial. Se recomienda deshabilitar el examen de directorios en producción. Revise cuidadosamente los directorios que se habilitan mediante UseStaticFiles o UseDirectoryBrowser. Todo el directorio y sus subdirectorios pasan a ser accesibles públicamente. Almacene los archivos adecuados para proporcionarlos al público en un directorio dedicado, como <content_root>/wwwroot. Separe estos archivos de las vistas MVC, de Razor Pages, de los archivos de configuración, etc.

  • Las direcciones URL para el contenido que se expone a través de UseDirectoryBrowser y UseStaticFiles están sujetas a la distinción entre mayúsculas y minúsculas, y a restricciones de caracteres del sistema de archivos subyacente. Por ejemplo, Windows no distingue entre mayúsculas y minúsculas, pero macOS y Linux sí.

  • Las aplicaciones de ASP.NET Core hospedadas en IIS usan el módulo de ASP.NET Core para reenviar todas las solicitudes a la aplicación, incluidas las solicitudes de archivos estáticos. No se utiliza el controlador de archivos estáticos de IIS y no tiene la posibilidad de controlar las solicitudes.

  • Complete los pasos siguientes en el Administrador de IIS para quitar el controlador de archivos estáticos de IIS en el nivel de servidor o de sitio web:

    1. Navegue hasta la característica Módulos.
    2. En la lista, seleccione StaticFileModule.
    3. Haga clic en Quitar en la barra lateral Acciones.

Advertencia

Si el controlador de archivos estáticos de IIS está habilitado y el módulo de ASP.NET Core no está configurado correctamente, se proporcionan archivos estáticos. Esto sucede, por ejemplo, si el archivo web.config no está implementado.

  • Coloque los archivos de código (incluidos .cs y .cshtml) fuera de la raíz web del proyecto de la aplicación. Por lo tanto, se crea una separación lógica entre el contenido del lado cliente de la aplicación y el código basado en servidor. Esto impide que se filtre el código del lado servidor.

Proporcionar archivos fuera de wwwroot mediante la actualización de IWebHostEnvironment.WebRootPath

Cuando IWebHostEnvironment.WebRootPath se establece en una carpeta distinta de wwwroot:

  • En el entorno de desarrollo, los recursos estáticos que se encuentran en wwwroot y en la propiedad IWebHostEnvironment.WebRootPath actualizada se proporcionan desde wwwroot.
  • En cualquier entorno distinto del de desarrollo, los recursos estáticos duplicados se proporcionan desde la carpeta IWebHostEnvironment.WebRootPath actualizada.

Considere una aplicación web creada con la plantilla web vacía:

  • Que contenga un archivo Index.html en wwwroot y wwwroot-custom.

  • Con el siguiente archivo Program.cs actualizado que establece WebRootPath = "wwwroot-custom":

    var builder = WebApplication.CreateBuilder(new WebApplicationOptions
    {
        Args = args,
        // Look for static files in "wwwroot-custom"
        WebRootPath = "wwwroot-custom"
    });
    
    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.UseStaticFiles();
    
    app.Run();
    

En el código anterior, las solicitudes a /:

  • En el entorno de desarrollo, devuelven wwwroot/Index.html
  • En cualquier entorno distinto del de desarrollo, devuelven wwwroot-custom/Index.html

Para asegurarse de que se devuelven los recursos de wwwroot-custom, use uno de los enfoques siguientes:

  • Elimine los recursos con nombre duplicado en wwwroot.

  • Establezca "ASPNETCORE_ENVIRONMENT" de Properties/launchSettings.json en cualquier valor distinto de "Development".

  • Deshabilite completamente los recursos web estáticos estableciendo <StaticWebAssetsEnabled>false</StaticWebAssetsEnabled> en el archivo del proyecto. ADVERTENCIA, al deshabilitar los recursos web estáticos, se deshabilitan las bibliotecas de clases de Razor.

  • Agregue el siguiente JSON al archivo del proyecto:

    <ItemGroup>
        <Content Remove="wwwroot\**" />
    </ItemGroup>
    

El código siguiente actualiza IWebHostEnvironment.WebRootPath a un valor que no es de desarrollo, lo que garantiza que se devuelva contenido duplicado de wwwroot-custom en lugar de wwwroot:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Examine Hosting environment: logging value
    EnvironmentName = Environments.Staging,
    WebRootPath = "wwwroot-custom"
});

var app = builder.Build();

app.Logger.LogInformation("ASPNETCORE_ENVIRONMENT: {env}",
      Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));

app.Logger.LogInformation("app.Environment.IsDevelopment(): {env}",
      app.Environment.IsDevelopment().ToString());

app.UseDefaultFiles();
app.UseStaticFiles();

app.Run();

Recursos adicionales

Por Rick Anderson y Kirk Larkin

Los archivos estáticos, como HTML, CSS, imágenes y JavaScript, son activos que una aplicación de ASP.NET Core proporciona directamente a los clientes de forma predeterminada.

Vea o descargue el código de ejemplo (cómo descargarlo)

Proporcionar archivos estáticos

Los archivos estáticos se almacenan en el directorio raíz web del proyecto. El directorio predeterminado es {content root}/wwwroot, pero se puede cambiar con el método UseWebRoot. Para más información, vea Raíz del contenido y Raíz web.

El método CreateDefaultBuilder establece la raíz de contenido en el directorio actual:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

El código anterior se creó con la plantilla de la aplicación web.

Se puede acceder a los archivos estáticos a través de una ruta de acceso relativa a la raíz web. Por ejemplo, las plantillas de proyecto Aplicación web contienen varias carpetas dentro de la carpeta wwwroot:

  • wwwroot
    • css
    • js
    • lib

Considere la posibilidad de crear la carpeta wwwroot/images y agregar el archivo wwwroot/images/MyImage.jpg. El formato del URI para acceder a un archivo en la carpeta images es https://<hostname>/images/<image_file_name>. Por ejemplo, https://localhost:5001/images/MyImage.jpg.

Suministro de archivos en la raíz web

Las plantillas de aplicación web predeterminadas llaman al método UseStaticFiles en Startup.Configure, que permite proporcionar archivos estáticos:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

La sobrecarga del método UseStaticFiles sin parámetros marca los archivos en la raíz web como que se pueden proporcionar. El siguiente marcado hace referencia a wwwroot/images/MyImage.jpg:

<img src="~/images/MyImage.jpg" class="img" alt="My image" />

En el código anterior, el carácter de tilde de la ñ ~/ apunta a la raíz web.

Proporcionar archivos fuera de la raíz web

Considere una jerarquía de directorios en la que residen fuera de la raíz web los archivos estáticos que se van a proporcionar:

  • wwwroot
    • css
    • images
    • js
  • MyStaticFiles
    • images
      • red-rose.jpg

Una solicitud puede acceder al archivo red-rose.jpg configurando el middleware de archivos estáticos como se muestra a continuación:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    // using Microsoft.Extensions.FileProviders;
    // using System.IO;
    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(env.ContentRootPath, "MyStaticFiles")),
        RequestPath = "/StaticFiles"
    });

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

En el código anterior, la jerarquía del directorio MyStaticFiles se expone públicamente a través del segmento de URI StaticFiles. Una solicitud a https://<hostname>/StaticFiles/images/red-rose.jpg sirve al archivo red-rose.jpg.

El siguiente marcado hace referencia a MyStaticFiles/images/red-rose.jpg:

<img src="~/StaticFiles/images/red-rose.jpg" class="img" alt="A red rose" />

Establecer encabezados de respuesta HTTP

Se puede usar un objeto StaticFileOptions para establecer encabezados de respuesta HTTP. Además de configurar el servicio de archivos estáticos desde la raíz web, el código siguiente establece el encabezado Cache-Control:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    const string cacheMaxAge = "604800";
    app.UseStaticFiles(new StaticFileOptions
    {
        OnPrepareResponse = ctx =>
        {
            // using Microsoft.AspNetCore.Http;
            ctx.Context.Response.Headers.Append(
                 "Cache-Control", $"public, max-age={cacheMaxAge}");
        }
    });

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

El código anterior establece la antigüedad máxima en 604 800 segundos (7 días).

Se han agregado encabezados de respuesta que muestran el encabezado Cache-Control

Autorización de archivos estáticos

Las plantillas de ASP.NET Core llaman a UseStaticFiles antes de llamar a UseAuthorization. La mayoría de las aplicaciones siguen este patrón. Cuando se llama al middleware de archivos estáticos antes de al middleware de autorización:

  • No se realizan comprobaciones de autorización en los archivos estáticos.
  • Los archivos estáticos que atiende el middleware de archivos estáticos, como los que están en wwwroot, son accesibles públicamente.

Para proporcionar archivos estáticos según su autorización:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    // wwwroot css, JavaScript, and images don't require authentication.
    app.UseStaticFiles();   

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider(
                     Path.Combine(env.ContentRootPath, "MyStaticFiles")),
        RequestPath = "/StaticFiles"
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddRazorPages();

        services.AddAuthorization(options =>
        {
            options.FallbackPolicy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
        });
    }

    // Remaining code ommitted for brevity.

En el código anterior, la directiva de autorización de reserva requiere que se autentiquen todos los usuarios. Los puntos de conexión como los controladores, Razor Pages, etc. que especifican sus propios requisitos de autorización no usan la directiva de autorización de reserva. Por ejemplo, Razor Pages, controladores o métodos de acción con [AllowAnonymous] o [Authorize(PolicyName="MyPolicy")] usan el atributo de autorización aplicado en lugar de la directiva de autorización de reserva.

RequireAuthenticatedUser agrega DenyAnonymousAuthorizationRequirement a la instancia actual, lo que exige que el usuario actual se autentique.

Los recursos estáticos en wwwroot son accesibles públicamente porque se llama al middleware de archivos estáticos predeterminado (app.UseStaticFiles();) antes de a UseAuthentication. Los recursos estáticos de la carpeta MyStaticFiles requieren autenticación. En el código de ejemplo se muestra esto.

Un enfoque alternativo a proporcionar archivos según la autorización es:

  • Almacénelos fuera de wwwroot y cualquier directorio al que el middleware de archivos estáticos tenga acceso.
  • Proporciónelos a través de un método de acción al que se aplica la autorización y devuelva un objeto FileResult:
[Authorize]
public IActionResult BannerImage()
{
    var filePath = Path.Combine(
        _env.ContentRootPath, "MyStaticFiles", "images", "red-rose.jpg");

    return PhysicalFile(filePath, "image/jpeg");
}

Examen de directorios

El examen de directorios permite enumerar directorios dentro de los directorios especificados.

Por motivos de seguridad, el examen de directorios está deshabilitado de forma predeterminada. Para obtener más información, consulte Consideraciones de seguridad para archivos estáticos.

Habilite el examen de directorios con:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddDirectoryBrowser();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    // using Microsoft.Extensions.FileProviders;
    // using System.IO;
    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(env.WebRootPath, "images")),
        RequestPath = "/MyImages"
    });

    app.UseDirectoryBrowser(new DirectoryBrowserOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(env.WebRootPath, "images")),
        RequestPath = "/MyImages"
    });

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

El código anterior permite el examen de directorios de la carpeta wwwroot/images usando la dirección URL https://<hostname>/MyImages, con vínculos a cada archivo y carpeta:

examen de directorios

Suministro de documentos predeterminados

El establecimiento de una página predeterminada proporciona a los visitantes un punto inicial en un sitio. Para servir un archivo predeterminado desde wwwroot sin necesidad de que la dirección URL de la solicitud incluya el nombre del archivo, llame al método UseDefaultFiles:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    app.UseDefaultFiles();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

Debe llamarse a UseDefaultFiles antes de a UseStaticFiles para proporcionar el archivo predeterminado. UseDefaultFiles es un sistema de reescritura de direcciones URL que no proporciona el archivo.

Con UseDefaultFiles, las solicitudes a una carpeta en wwwroot buscan:

  • default.htm
  • default.html
  • index.htm
  • index.html

El primer archivo que se encuentra en la lista se sirve como si la solicitud incluyera el nombre de archivo. La dirección URL del explorador sigue reflejando el URI solicitado.

El código siguiente cambia el nombre de archivo predeterminado a mydefault.html:

var options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();

El código siguiente muestra Startup.Configure con el código anterior:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    var options = new DefaultFilesOptions();
    options.DefaultFileNames.Clear();
    options.DefaultFileNames.Add("mydefault.html");
    app.UseDefaultFiles(options);
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

UseFileServer para documentos predeterminados

UseFileServer combina la funcionalidad de UseStaticFiles y UseDefaultFiles y, opcionalmente, la de UseDirectoryBrowser.

Llame a app.UseFileServer para poder proporcionar archivos estáticos y el archivo predeterminado. El examen de directorios no está habilitado. El código siguiente muestra Startup.Configure con UseFileServer:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    app.UseFileServer();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

El código siguiente permite proporcionar archivos estáticos, el archivo predeterminado y el examen de directorios:

app.UseFileServer(enableDirectoryBrowsing: true);

El código siguiente muestra Startup.Configure con el código anterior:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    app.UseFileServer(enableDirectoryBrowsing: true);

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

Tenga en cuenta la siguiente jerarquía de directorios:

  • wwwroot
    • css
    • images
    • js
  • MyStaticFiles
    • images
      • MyImage.jpg
    • default.html

El código siguiente permite proporcionar archivos estáticos, el archivo predeterminado y el examen de directorios de MyStaticFiles:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddDirectoryBrowser();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    app.UseStaticFiles(); // For the wwwroot folder.

    // using Microsoft.Extensions.FileProviders;
    // using System.IO;
    app.UseFileServer(new FileServerOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(env.ContentRootPath, "MyStaticFiles")),
        RequestPath = "/StaticFiles",
        EnableDirectoryBrowsing = true
    });

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

Se debe llamar a AddDirectoryBrowser cuando el valor de la propiedad EnableDirectoryBrowsing es true.

Al usar la jerarquía de archivos y el código anterior, las direcciones URL se resuelven como se indica a continuación:

Identificador URI Respuesta
https://<hostname>/StaticFiles/images/MyImage.jpg MyStaticFiles/images/MyImage.jpg
https://<hostname>/StaticFiles MyStaticFiles/default.html

Si no existe ningún archivo con el nombre predeterminado en el directorio MyStaticFiles, https://<hostname>/StaticFiles devuelve la lista de directorios con vínculos activos:

Lista de archivos estáticos

UseDefaultFiles y UseDirectoryBrowser realizan un redireccionamiento del lado cliente desde el URI de destino sin una / final hasta el URI de destino una / final. Por ejemplo, desde https://<hostname>/StaticFiles hasta https://<hostname>/StaticFiles/. Las direcciones URL relativas dentro del directorio StaticFiles no son válidas sin una barra diagonal final (/).

FileExtensionContentTypeProvider

La clase FileExtensionContentTypeProvider contiene una propiedad Mappings que actúa como una asignación de extensiones de archivo para tipos de contenido MIME. En el ejemplo siguiente, se asignan varias extensiones de archivo a los tipos MIME conocidos. Se reemplaza la extensión .rtf y se quita .mp4:

// using Microsoft.AspNetCore.StaticFiles;
// using Microsoft.Extensions.FileProviders;
// using System.IO;

// Set up custom content types - associating file extension to MIME type
var provider = new FileExtensionContentTypeProvider();
// Add new mappings
provider.Mappings[".myapp"] = "application/x-msdownload";
provider.Mappings[".htm3"] = "text/html";
provider.Mappings[".image"] = "image/png";
// Replace an existing mapping
provider.Mappings[".rtf"] = "application/x-msdownload";
// Remove MP4 videos.
provider.Mappings.Remove(".mp4");

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(env.WebRootPath, "images")),
    RequestPath = "/MyImages",
    ContentTypeProvider = provider
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(env.WebRootPath, "images")),
    RequestPath = "/MyImages"
});

El código siguiente muestra Startup.Configure con el código anterior:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    // using Microsoft.AspNetCore.StaticFiles;
    // using Microsoft.Extensions.FileProviders;
    // using System.IO;

    // Set up custom content types - associating file extension to MIME type
    var provider = new FileExtensionContentTypeProvider();
    // Add new mappings
    provider.Mappings[".myapp"] = "application/x-msdownload";
    provider.Mappings[".htm3"] = "text/html";
    provider.Mappings[".image"] = "image/png";
    // Replace an existing mapping
    provider.Mappings[".rtf"] = "application/x-msdownload";
    // Remove MP4 videos.
    provider.Mappings.Remove(".mp4");

    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(env.WebRootPath, "images")),
        RequestPath = "/MyImages",
        ContentTypeProvider = provider
    });

    app.UseDirectoryBrowser(new DirectoryBrowserOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(env.WebRootPath, "images")),
        RequestPath = "/MyImages"
    });

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

Vea Tipos de contenido MIME.

Tipos de contenido no estándar

El middleware de archivos estáticos entiende casi 400 tipos de contenido de archivo conocidos. Si el usuario solicita un archivo con un tipo de archivo desconocido, el middleware de archivos estáticos pasa la solicitud al siguiente middleware de la canalización. Si ningún middleware se ocupa de la solicitud, se devuelve una respuesta 404 No encontrado. Si se habilita la exploración de directorios, se muestra un vínculo al archivo en una lista de directorios.

El código siguiente permite proporcionar tipos desconocidos y procesa el archivo desconocido como una imagen:

app.UseStaticFiles(new StaticFileOptions
{
    ServeUnknownFileTypes = true,
    DefaultContentType = "image/png"
});

El código siguiente muestra Startup.Configure con el código anterior:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    app.UseStaticFiles(new StaticFileOptions
    {
        ServeUnknownFileTypes = true,
        DefaultContentType = "image/png"
    });

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

Con el código anterior, una solicitud para un archivo con un tipo de contenido desconocido se devuelve como una imagen.

Advertencia

Habilitar ServeUnknownFileTypes supone un riesgo para la seguridad. Está deshabilitado de forma predeterminada y no se recomienda su uso. FileExtensionContentTypeProvider proporciona una alternativa más segura a ofrecer archivos con extensiones no estándar.

Entrega de archivos desde varias ubicaciones

UseStaticFiles y UseFileServer tienen como valor predeterminado el proveedor de archivos que apunta a wwwroot. Se pueden proporcionar instancias adicionales de UseStaticFiles y UseFileServer con otros proveedores de archivos para proporcionar archivos desde otras ubicaciones. Para más información, consulte este problema de GitHub.

Consideraciones de seguridad para archivos estáticos

Advertencia

UseDirectoryBrowser y UseStaticFiles pueden producir pérdidas de información confidencial. Se recomienda deshabilitar el examen de directorios en producción. Revise cuidadosamente los directorios que se habilitan mediante UseStaticFiles o UseDirectoryBrowser. Todo el directorio y sus subdirectorios pasan a ser accesibles públicamente. Almacene los archivos adecuados para proporcionarlos al público en un directorio dedicado, como <content_root>/wwwroot. Separe estos archivos de las vistas MVC, de Razor Pages, de los archivos de configuración, etc.

  • Las direcciones URL para el contenido que se expone a través de UseDirectoryBrowser y UseStaticFiles están sujetas a la distinción entre mayúsculas y minúsculas, y a restricciones de caracteres del sistema de archivos subyacente. Por ejemplo, Windows no distingue entre mayúsculas y minúsculas, pero macOS y Linux sí.

  • Las aplicaciones de ASP.NET Core hospedadas en IIS usan el módulo de ASP.NET Core para reenviar todas las solicitudes a la aplicación, incluidas las solicitudes de archivos estáticos. No se utiliza el controlador de archivos estáticos de IIS y no tiene la posibilidad de controlar las solicitudes.

  • Complete los pasos siguientes en el Administrador de IIS para quitar el controlador de archivos estáticos de IIS en el nivel de servidor o de sitio web:

    1. Navegue hasta la característica Módulos.
    2. En la lista, seleccione StaticFileModule.
    3. Haga clic en Quitar en la barra lateral Acciones.

Advertencia

Si el controlador de archivos estáticos de IIS está habilitado y el módulo de ASP.NET Core no está configurado correctamente, se proporcionan archivos estáticos. Esto sucede, por ejemplo, si el archivo web.config no está implementado.

  • Coloque los archivos de código (incluidos .cs y .cshtml) fuera de la raíz web del proyecto de la aplicación. Por lo tanto, se crea una separación lógica entre el contenido del lado cliente de la aplicación y el código basado en servidor. Esto impide que se filtre el código del lado servidor.

Recursos adicionales