Static files in ASP.NET Core

Note

This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.

Warning

This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 9 version of this article.

Important

This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

For the current release, see the .NET 9 version of this article.

By Rick Anderson

Static files, such as HTML, CSS, images, and JavaScript, are assets an ASP.NET Core app serves directly to clients by default.

For Blazor static files guidance, which adds to or supersedes the guidance in this article, see ASP.NET Core Blazor static files.

Serve static files

Static files are stored within the project's web root directory. The default directory is {content root}/wwwroot, but it can be changed with the UseWebRoot method. For more information, see Content root and Web root.

The CreateBuilder method sets the content root to the current directory:

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.MapStaticAssets();

app.UseAuthorization();

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

app.Run();

Static files are accessible via a path relative to the web root. For example, the Web Application project templates contain several folders within the wwwroot folder:

  • wwwroot
    • css
    • js
    • lib

Consider an app with the wwwroot/images/MyImage.jpg file. The URI format to access a file in the images folder is https://<hostname>/images/<image_file_name>. For example, https://localhost:5001/images/MyImage.jpg

MapStaticAssets

Creating performant web apps requires optimizing asset delivery to the browser. Possible optimizations include:

  • Serve a given asset once until the file changes or the browser clears its cache. Set the ETag header.
  • Prevent the browser from using old or stale assets after an app is updated. Set the Last-Modified header.
  • Set up proper caching headers.
  • Use caching middleware.
  • Serve compressed versions of the assets when possible.
  • Use a CDN to serve the assets closer to the user.
  • Minimize the size of assets served to the browser. This optimization doesn't include minification.

MapStaticAssets are routing endpoint conventions that optimize the delivery of static assets in an app. It's designed to work with all UI frameworks, including Blazor, Razor Pages, and MVC.

UseStaticFiles also serves static files, but it doesn't provide the same level of optimization as MapStaticAssets. For a comparison of UseStaticFiles and MapStaticAssets, see Optimizing static web asset delivery .

Serve files in web root

The default web app templates call the MapStaticAssets method in Program.cs, which enables static files to be served:

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.MapStaticAssets();

app.UseAuthorization();

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

app.Run();

The parameterless UseStaticFiles method overload marks the files in web root as servable. The following markup references wwwroot/images/MyImage.jpg:

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

In the preceding markup, the tilde character ~ points to the web root.

Serve files outside of web root

Consider a directory hierarchy in which the static files to be served reside outside of the web root:

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

A request can access the red-rose.jpg file by configuring the Static File Middleware as follows:

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();    //Serve files from wwwroot
app.UseStaticFiles(new StaticFileOptions
 {
     FileProvider = new PhysicalFileProvider(
            Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
     RequestPath = "/StaticFiles"
 });

app.UseAuthorization();

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

app.Run();

In the preceding code, the MyStaticFiles directory hierarchy is exposed publicly via the StaticFiles URI segment. A request to https://<hostname>/StaticFiles/images/red-rose.jpg serves the red-rose.jpg file.

The following markup references MyStaticFiles/images/red-rose.jpg:

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

To serve files from multiple locations, see Serve files from multiple locations.

Set HTTP response headers

A StaticFileOptions object can be used to set HTTP response headers. In addition to configuring static file serving from the web root, the following code sets the Cache-Control header:

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().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

The preceding code makes static files publicly available in the local cache for one week.

Static file authorization

The ASP.NET Core templates call MapStaticAssets before calling UseAuthorization. Most apps follow this pattern. When the Static File Middleware is called before the authorization middleware:

  • No authorization checks are performed on the static files.
  • Static files served by the Static File Middleware, such as those under wwwroot, are publicly accessible.

To serve static files based on authorization:

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();

In the preceding code, the fallback authorization policy requires all users to be authenticated. Endpoints such as controllers, Razor Pages, etc that specify their own authorization requirements don't use the fallback authorization policy. For example, Razor Pages, controllers, or action methods with [AllowAnonymous] or [Authorize(PolicyName="MyPolicy")] use the applied authorization attribute rather than the fallback authorization policy.

RequireAuthenticatedUser adds DenyAnonymousAuthorizationRequirement to the current instance, which enforces that the current user is authenticated.

Static assets under wwwroot are publicly accessible because the default Static File Middleware (app.UseStaticFiles();) is called before UseAuthentication. Static assets in the MyStaticFiles folder require authentication. The sample code demonstrates this.

An alternative approach to serve files based on authorization is to:

  • Store them outside of wwwroot and any directory accessible to the Static File Middleware.

  • Serve them via an action method to which authorization is applied and return a FileResult object:

    [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");
        }
    }
    

The preceding approach requires a page or endpoint per file. The following code returns files or uploads files for authenticated users:

app.MapGet("/files/{fileName}",  IResult (string fileName) => 
    {
        var filePath = GetOrCreateFilePath(fileName);

        if (File.Exists(filePath))
        {
           return TypedResults.PhysicalFile(filePath, fileDownloadName: $"{fileName}");
        }

        return TypedResults.NotFound("No file found with the supplied file name");
    })
    .WithName("GetFileByName")
    .RequireAuthorization("AuthenticatedUsers");

app.MapPost("/files", 
       async (IFormFile file, LinkGenerator linker, HttpContext context) =>
    {
        // Don't rely on the file.FileName as it is only metadata that can be
        // manipulated by the end-user. See the `Utilities.IsFileValid` method that
        // takes an IFormFile and validates its signature within the
        // AllowedFileSignatures
        
        var fileSaveName = Guid.NewGuid().ToString("N") 
                           + Path.GetExtension(file.FileName);
        await SaveFileWithCustomFileName(file, fileSaveName);
        
        context.Response.Headers.Append("Location",
                                     linker.GetPathByName(context, "GetFileByName", 
                                     new { fileName = fileSaveName}));
        return TypedResults.Ok("File Uploaded Successfully!");
    })
    .RequireAuthorization("AdminsOnly");

app.Run();

IFormFile in the preceding sample uses memory buffer for uploading. For handling large file use streaming. See Upload large files with streaming.

See the StaticFileAuth GitHub folder for the complete sample.

Directory browsing

Directory browsing allows directory listing within specified directories.

Directory browsing is disabled by default for security reasons. For more information, see Security considerations for static files.

Enable directory browsing with AddDirectoryBrowser and 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.MapStaticAssets();

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().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

The preceding code allows directory browsing of the wwwroot/images folder using the URL https://<hostname>/MyImages, with links to each file and folder:

directory browsing

AddDirectoryBrowser adds services required by the directory browsing middleware, including HtmlEncoder. These services may be added by other calls, such as AddRazorPages, but we recommend calling AddDirectoryBrowser to ensure the services are added in all apps.

Serve default documents

Setting a default page provides visitors a starting point on a site. To serve a default file from wwwroot without requiring the request URL to include the file's name, call the UseDefaultFiles method:

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().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

UseDefaultFiles must be called before UseStaticFiles to serve the default file. UseDefaultFiles is a URL rewriter that doesn't serve the file.

With UseDefaultFiles, requests to a folder in wwwroot search for:

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

The first file found from the list is served as though the request included the file's name. The browser URL continues to reflect the URI requested. For example, in the sample app, a request to https://localhost:<port>/def/ serves default.html from wwwroot/def.

The following code changes the default file name to 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().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

UseFileServer for default documents

UseFileServer combines the functionality of UseStaticFiles, UseDefaultFiles, and optionally UseDirectoryBrowser.

Call app.UseFileServer to enable the serving of static files and the default file. Directory browsing isn't enabled:

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().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

The following code enables the serving of static files, the default file, and directory browsing:

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().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

Consider the following directory hierarchy:

  • wwwroot
    • css
    • images
    • js
  • MyStaticFiles
    • defaultFiles
      • default.html
      • image3.png
    • images
      • MyImage.jpg

The following code enables the serving of static files, the default file, and directory browsing of 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().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

AddDirectoryBrowser must be called when the EnableDirectoryBrowsing property value is true.

Using the preceding file hierarchy and code, URLs resolve as follows:

URI Response
https://<hostname>/StaticFiles/images/MyImage.jpg MyStaticFiles/images/MyImage.jpg
https://<hostname>/StaticFiles directory listing
https://<hostname>/StaticFiles/defaultFiles MyStaticFiles/defaultFiles/default.html
https://<hostname>/StaticFiles/defaultFiles/image3.png MyStaticFiles/defaultFiles//image3.png

If no default-named file exists in the MyStaticFiles directory, https://<hostname>/StaticFiles returns the directory listing with clickable links:

Static files list

UseDefaultFiles and UseDirectoryBrowser perform a client-side redirect from the target URI without a trailing / to the target URI with a trailing /. For example, from https://<hostname>/StaticFiles to https://<hostname>/StaticFiles/. Relative URLs within the StaticFiles directory are invalid without a trailing slash (/) unless the RedirectToAppendTrailingSlash option of DefaultFilesOptions is used.

FileExtensionContentTypeProvider

The FileExtensionContentTypeProvider class contains a Mappings property that serves as a mapping of file extensions to MIME content types. In the following sample, several file extensions are mapped to known MIME types. The .rtf extension is replaced, and .mp4 is removed:

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().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

See MIME content types.

Non-standard content types

The Static File Middleware understands almost 400 known file content types. If the user requests a file with an unknown file type, the Static File Middleware passes the request to the next middleware in the pipeline. If no middleware handles the request, a 404 Not Found response is returned. If directory browsing is enabled, a link to the file is displayed in a directory listing.

The following code enables serving unknown types and renders the unknown file as an image:

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().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

With the preceding code, a request for a file with an unknown content type is returned as an image.

Warning

Enabling ServeUnknownFileTypes is a security risk. It's disabled by default, and its use is discouraged. FileExtensionContentTypeProvider provides a safer alternative to serving files with non-standard extensions.

Serve files from multiple locations

Consider the following Razor page which displays the /MyStaticFiles/image3.png file:

@page

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

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

UseStaticFiles and UseFileServer default to the file provider pointing at wwwroot. Additional instances of UseStaticFiles and UseFileServer can be provided with other file providers to serve files from other locations. The following example calls UseStaticFiles twice to serve files from both wwwroot and MyStaticFiles:

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

Using the preceding code:

The following code updates the WebRootFileProvider, which enables the Image Tag Helper to provide a version:

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.MapStaticAssets();

Note

The preceding approach applies to Razor Pages and MVC apps. For guidance that applies to Blazor Web Apps, see ASP.NET Core Blazor static files.

Security considerations for static files

Warning

UseDirectoryBrowser and UseStaticFiles can leak secrets. Disabling directory browsing in production is highly recommended. Carefully review which directories are enabled via UseStaticFiles or UseDirectoryBrowser. The entire directory and its sub-directories become publicly accessible. Store files suitable for serving to the public in a dedicated directory, such as <content_root>/wwwroot. Separate these files from MVC views, Razor Pages, configuration files, etc.

  • The URLs for content exposed with UseDirectoryBrowser, UseStaticFiles, and MapStaticAssets are subject to the case sensitivity and character restrictions of the underlying file system. For example, Windows is case insensitive, but macOS and Linux aren't.

  • ASP.NET Core apps hosted in IIS use the ASP.NET Core Module to forward all requests to the app, including static file requests. The IIS static file handler isn't used and has no chance to handle requests.

  • Complete the following steps in IIS Manager to remove the IIS static file handler at the server or website level:

    1. Navigate to the Modules feature.
    2. Select StaticFileModule in the list.
    3. Click Remove in the Actions sidebar.

Warning

If the IIS static file handler is enabled and the ASP.NET Core Module is configured incorrectly, static files are served. This happens, for example, if the web.config file isn't deployed.

  • Place code files, including .cs and .cshtml, outside of the app project's web root. A logical separation is therefore created between the app's client-side content and server-based code. This prevents server-side code from being leaked.

Serve files outside wwwroot by updating IWebHostEnvironment.WebRootPath

When IWebHostEnvironment.WebRootPath is set to a folder other than wwwroot:

  • In the development environment, static assets found in both wwwroot and the updated IWebHostEnvironment.WebRootPath are served from wwwroot.
  • In any environment other than development, duplicate static assets are served from the updated IWebHostEnvironment.WebRootPath folder.

Consider a web app created with the empty web template:

  • Containing an Index.html file in wwwroot and wwwroot-custom.

  • With the following updated Program.cs file that sets 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.MapStaticAssets();
    
    app.Run();
    

In the preceding code, requests to /:

  • In the development environment return wwwroot/Index.html
  • In any environment other than development return wwwroot-custom/Index.html

To ensure assets from wwwroot-custom are returned, use one of the following approaches:

  • Delete duplicate named assets in wwwroot.

  • Set "ASPNETCORE_ENVIRONMENT" in Properties/launchSettings.json to any value other than "Development".

  • Completely disable static web assets by setting <StaticWebAssetsEnabled>false</StaticWebAssetsEnabled> in the project file. WARNING, disabling static web assets disables Razor Class Libraries.

  • Add the following XML to the project file:

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

The following code updates IWebHostEnvironment.WebRootPath to a non development value, guaranteeing duplicate content is returned from wwwroot-custom rather than 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.MapStaticAssets();

app.Run();

Additional resources

By Rick Anderson

Static files, such as HTML, CSS, images, and JavaScript, are assets an ASP.NET Core app serves directly to clients by default.

Serve static files

Static files are stored within the project's web root directory. The default directory is {content root}/wwwroot, but it can be changed with the UseWebRoot method. For more information, see Content root and Web root.

The CreateBuilder method sets the content root to the current directory:

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();

Static files are accessible via a path relative to the web root. For example, the Web Application project templates contain several folders within the wwwroot folder:

  • wwwroot
    • css
    • js
    • lib

Consider creating the wwwroot/images folder and adding the wwwroot/images/MyImage.jpg file. The URI format to access a file in the images folder is https://<hostname>/images/<image_file_name>. For example, https://localhost:5001/images/MyImage.jpg

Serve files in web root

The default web app templates call the UseStaticFiles method in Program.cs, which enables static files to be served:

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();

The parameterless UseStaticFiles method overload marks the files in web root as servable. The following markup references wwwroot/images/MyImage.jpg:

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

In the preceding markup, the tilde character ~ points to the web root.

Serve files outside of web root

Consider a directory hierarchy in which the static files to be served reside outside of the web root:

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

A request can access the red-rose.jpg file by configuring the Static File Middleware as follows:

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();

In the preceding code, the MyStaticFiles directory hierarchy is exposed publicly via the StaticFiles URI segment. A request to https://<hostname>/StaticFiles/images/red-rose.jpg serves the red-rose.jpg file.

The following markup references MyStaticFiles/images/red-rose.jpg:

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

To serve files from multiple locations, see Serve files from multiple locations.

Set HTTP response headers

A StaticFileOptions object can be used to set HTTP response headers. In addition to configuring static file serving from the web root, the following code sets the Cache-Control header:

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();

The preceding code makes static files publicly available in the local cache for one week (604800 seconds).

Static file authorization

The ASP.NET Core templates call UseStaticFiles before calling UseAuthorization. Most apps follow this pattern. When the Static File Middleware is called before the authorization middleware:

  • No authorization checks are performed on the static files.
  • Static files served by the Static File Middleware, such as those under wwwroot, are publicly accessible.

To serve static files based on authorization:

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();

In the preceding code, the fallback authorization policy requires all users to be authenticated. Endpoints such as controllers, Razor Pages, etc that specify their own authorization requirements don't use the fallback authorization policy. For example, Razor Pages, controllers, or action methods with [AllowAnonymous] or [Authorize(PolicyName="MyPolicy")] use the applied authorization attribute rather than the fallback authorization policy.

RequireAuthenticatedUser adds DenyAnonymousAuthorizationRequirement to the current instance, which enforces that the current user is authenticated.

Static assets under wwwroot are publicly accessible because the default Static File Middleware (app.UseStaticFiles();) is called before UseAuthentication. Static assets in the MyStaticFiles folder require authentication. The sample code demonstrates this.

An alternative approach to serve files based on authorization is to:

  • Store them outside of wwwroot and any directory accessible to the Static File Middleware.

  • Serve them via an action method to which authorization is applied and return a FileResult object:

    [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");
        }
    }
    

The preceding approach requires a page or endpoint per file. The following code returns files or uploads files for authenticated users:

app.MapGet("/files/{fileName}",  IResult (string fileName) => 
    {
        var filePath = GetOrCreateFilePath(fileName);

        if (File.Exists(filePath))
        {
            return TypedResults.PhysicalFile(filePath, fileDownloadName: $"{fileName}");
        }

        return TypedResults.NotFound("No file found with the supplied file name");
    })
    .WithName("GetFileByName")
    .RequireAuthorization("AuthenticatedUsers");

// IFormFile uses memory buffer for uploading. For handling large file use streaming instead.
// https://learn.microsoft.com/aspnet/core/mvc/models/file-uploads#upload-large-files-with-streaming
app.MapPost("/files", async (IFormFile file, LinkGenerator linker, HttpContext context) =>
    {
        // Don't rely on the file.FileName as it is only metadata that can be manipulated by the end-user
        // Take a look at the `Utilities.IsFileValid` method that takes an IFormFile and validates its signature within the AllowedFileSignatures
        
        var fileSaveName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
        await SaveFileWithCustomFileName(file, fileSaveName);
        
        context.Response.Headers.Append("Location", linker.GetPathByName(context, "GetFileByName", new { fileName = fileSaveName}));
        return TypedResults.Ok("File Uploaded Successfully!");
    })
    .RequireAuthorization("AdminsOnly");

app.Run();

See the StaticFileAuth GitHub folder for the complete sample.

Directory browsing

Directory browsing allows directory listing within specified directories.

Directory browsing is disabled by default for security reasons. For more information, see Security considerations for static files.

Enable directory browsing with AddDirectoryBrowser and 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();

The preceding code allows directory browsing of the wwwroot/images folder using the URL https://<hostname>/MyImages, with links to each file and folder:

directory browsing

AddDirectoryBrowser adds services required by the directory browsing middleware, including HtmlEncoder. These services may be added by other calls, such as AddRazorPages, but we recommend calling AddDirectoryBrowser to ensure the services are added in all apps.

Serve default documents

Setting a default page provides visitors a starting point on a site. To serve a default file from wwwroot without requiring the request URL to include the file's name, call the UseDefaultFiles method:

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();

UseDefaultFiles must be called before UseStaticFiles to serve the default file. UseDefaultFiles is a URL rewriter that doesn't serve the file.

With UseDefaultFiles, requests to a folder in wwwroot search for:

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

The first file found from the list is served as though the request included the file's name. The browser URL continues to reflect the URI requested.

The following code changes the default file name to 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 for default documents

UseFileServer combines the functionality of UseStaticFiles, UseDefaultFiles, and optionally UseDirectoryBrowser.

Call app.UseFileServer to enable the serving of static files and the default file. Directory browsing isn't enabled:

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();

The following code enables the serving of static files, the default file, and directory browsing:

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();

Consider the following directory hierarchy:

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

The following code enables the serving of static files, the default file, and directory browsing of 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();

AddDirectoryBrowser must be called when the EnableDirectoryBrowsing property value is true.

Using the preceding file hierarchy and code, URLs resolve as follows:

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

If no default-named file exists in the MyStaticFiles directory, https://<hostname>/StaticFiles returns the directory listing with clickable links:

Static files list

UseDefaultFiles and UseDirectoryBrowser perform a client-side redirect from the target URI without a trailing / to the target URI with a trailing /. For example, from https://<hostname>/StaticFiles to https://<hostname>/StaticFiles/. Relative URLs within the StaticFiles directory are invalid without a trailing slash (/) unless the RedirectToAppendTrailingSlash option of DefaultFilesOptions is used.

FileExtensionContentTypeProvider

The FileExtensionContentTypeProvider class contains a Mappings property that serves as a mapping of file extensions to MIME content types. In the following sample, several file extensions are mapped to known MIME types. The .rtf extension is replaced, and .mp4 is removed:

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();

See MIME content types.

Non-standard content types

The Static File Middleware understands almost 400 known file content types. If the user requests a file with an unknown file type, the Static File Middleware passes the request to the next middleware in the pipeline. If no middleware handles the request, a 404 Not Found response is returned. If directory browsing is enabled, a link to the file is displayed in a directory listing.

The following code enables serving unknown types and renders the unknown file as an image:

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();

With the preceding code, a request for a file with an unknown content type is returned as an image.

Warning

Enabling ServeUnknownFileTypes is a security risk. It's disabled by default, and its use is discouraged. FileExtensionContentTypeProvider provides a safer alternative to serving files with non-standard extensions.

Serve files from multiple locations

Consider the following Razor page which displays the /MyStaticFiles/image3.png file:

@page

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

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

UseStaticFiles and UseFileServer default to the file provider pointing at wwwroot. Additional instances of UseStaticFiles and UseFileServer can be provided with other file providers to serve files from other locations. The following example calls UseStaticFiles twice to serve files from both wwwroot and MyStaticFiles:

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

Using the preceding code:

The following code updates the WebRootFileProvider, which enables the Image Tag Helper to provide a version:

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();

Note

The preceding approach applies to Razor Pages and MVC apps. For guidance that applies to Blazor Web Apps, see ASP.NET Core Blazor static files.

Security considerations for static files

Warning

UseDirectoryBrowser and UseStaticFiles can leak secrets. Disabling directory browsing in production is highly recommended. Carefully review which directories are enabled via UseStaticFiles or UseDirectoryBrowser. The entire directory and its sub-directories become publicly accessible. Store files suitable for serving to the public in a dedicated directory, such as <content_root>/wwwroot. Separate these files from MVC views, Razor Pages, configuration files, etc.

  • The URLs for content exposed with UseDirectoryBrowser and UseStaticFiles are subject to the case sensitivity and character restrictions of the underlying file system. For example, Windows is case insensitive, but macOS and Linux aren't.

  • ASP.NET Core apps hosted in IIS use the ASP.NET Core Module to forward all requests to the app, including static file requests. The IIS static file handler isn't used and has no chance to handle requests.

  • Complete the following steps in IIS Manager to remove the IIS static file handler at the server or website level:

    1. Navigate to the Modules feature.
    2. Select StaticFileModule in the list.
    3. Click Remove in the Actions sidebar.

Warning

If the IIS static file handler is enabled and the ASP.NET Core Module is configured incorrectly, static files are served. This happens, for example, if the web.config file isn't deployed.

  • Place code files, including .cs and .cshtml, outside of the app project's web root. A logical separation is therefore created between the app's client-side content and server-based code. This prevents server-side code from being leaked.

Serve files outside wwwroot by updating IWebHostEnvironment.WebRootPath

When IWebHostEnvironment.WebRootPath is set to a folder other than wwwroot:

  • In the development environment, static assets found in both wwwroot and the updated IWebHostEnvironment.WebRootPath are served from wwwroot.
  • In any environment other than development, duplicate static assets are served from the updated IWebHostEnvironment.WebRootPath folder.

Consider a web app created with the empty web template:

  • Containing an Index.html file in wwwroot and wwwroot-custom.

  • With the following updated Program.cs file that sets 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();
    

In the preceding code, requests to /:

  • In the development environment return wwwroot/Index.html
  • In any environment other than development return wwwroot-custom/Index.html

To ensure assets from wwwroot-custom are returned, use one of the following approaches:

  • Delete duplicate named assets in wwwroot.

  • Set "ASPNETCORE_ENVIRONMENT" in Properties/launchSettings.json to any value other than "Development".

  • Completely disable static web assets by setting <StaticWebAssetsEnabled>false</StaticWebAssetsEnabled> in the project file. WARNING, disabling static web assets disables Razor Class Libraries.

  • Add the following JSON to the project file:

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

The following code updates IWebHostEnvironment.WebRootPath to a non development value, guaranteeing duplicate content is returned from wwwroot-custom rather than 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();

Additional resources

By Rick Anderson and Kirk Larkin

Static files, such as HTML, CSS, images, and JavaScript, are assets an ASP.NET Core app serves directly to clients by default.

Serve static files

Static files are stored within the project's web root directory. The default directory is {content root}/wwwroot, but it can be changed with the UseWebRoot method. For more information, see Content root and Web root.

The CreateBuilder method sets the content root to the current directory:

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();

Static files are accessible via a path relative to the web root. For example, the Web Application project templates contain several folders within the wwwroot folder:

  • wwwroot
    • css
    • js
    • lib

Consider creating the wwwroot/images folder and adding the wwwroot/images/MyImage.jpg file. The URI format to access a file in the images folder is https://<hostname>/images/<image_file_name>. For example, https://localhost:5001/images/MyImage.jpg

Serve files in web root

The default web app templates call the UseStaticFiles method in Program.cs, which enables static files to be served:

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();

The parameterless UseStaticFiles method overload marks the files in web root as servable. The following markup references wwwroot/images/MyImage.jpg:

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

In the preceding markup, the tilde character ~ points to the web root.

Serve files outside of web root

Consider a directory hierarchy in which the static files to be served reside outside of the web root:

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

A request can access the red-rose.jpg file by configuring the Static File Middleware as follows:

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();

In the preceding code, the MyStaticFiles directory hierarchy is exposed publicly via the StaticFiles URI segment. A request to https://<hostname>/StaticFiles/images/red-rose.jpg serves the red-rose.jpg file.

The following markup references MyStaticFiles/images/red-rose.jpg:

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

To serve files from multiple locations, see Serve files from multiple locations.

Set HTTP response headers

A StaticFileOptions object can be used to set HTTP response headers. In addition to configuring static file serving from the web root, the following code sets the Cache-Control header:

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();

The preceding code makes static files publicly available in the local cache for one week (604800 seconds).

Static file authorization

The ASP.NET Core templates call UseStaticFiles before calling UseAuthorization. Most apps follow this pattern. When the Static File Middleware is called before the authorization middleware:

  • No authorization checks are performed on the static files.
  • Static files served by the Static File Middleware, such as those under wwwroot, are publicly accessible.

To serve static files based on authorization:

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();

In the preceding code, the fallback authorization policy requires all users to be authenticated. Endpoints such as controllers, Razor Pages, etc that specify their own authorization requirements don't use the fallback authorization policy. For example, Razor Pages, controllers, or action methods with [AllowAnonymous] or [Authorize(PolicyName="MyPolicy")] use the applied authorization attribute rather than the fallback authorization policy.

RequireAuthenticatedUser adds DenyAnonymousAuthorizationRequirement to the current instance, which enforces that the current user is authenticated.

Static assets under wwwroot are publicly accessible because the default Static File Middleware (app.UseStaticFiles();) is called before UseAuthentication. Static assets in the MyStaticFiles folder require authentication. The sample code demonstrates this.

An alternative approach to serve files based on authorization is to:

  • Store them outside of wwwroot and any directory accessible to the Static File Middleware.
  • Serve them via an action method to which authorization is applied and return a FileResult object:
[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");
    }
}

Directory browsing

Directory browsing allows directory listing within specified directories.

Directory browsing is disabled by default for security reasons. For more information, see Security considerations for static files.

Enable directory browsing with AddDirectoryBrowser and 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();

The preceding code allows directory browsing of the wwwroot/images folder using the URL https://<hostname>/MyImages, with links to each file and folder:

directory browsing

AddDirectoryBrowser adds services required by the directory browsing middleware, including HtmlEncoder. These services may be added by other calls, such as AddRazorPages, but we recommend calling AddDirectoryBrowser to ensure the services are added in all apps.

Serve default documents

Setting a default page provides visitors a starting point on a site. To serve a default file from wwwroot without requiring the request URL to include the file's name, call the UseDefaultFiles method:

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();

UseDefaultFiles must be called before UseStaticFiles to serve the default file. UseDefaultFiles is a URL rewriter that doesn't serve the file.

With UseDefaultFiles, requests to a folder in wwwroot search for:

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

The first file found from the list is served as though the request included the file's name. The browser URL continues to reflect the URI requested.

The following code changes the default file name to 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 for default documents

UseFileServer combines the functionality of UseStaticFiles, UseDefaultFiles, and optionally UseDirectoryBrowser.

Call app.UseFileServer to enable the serving of static files and the default file. Directory browsing isn't enabled:

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();

The following code enables the serving of static files, the default file, and directory browsing:

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();

Consider the following directory hierarchy:

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

The following code enables the serving of static files, the default file, and directory browsing of 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();

AddDirectoryBrowser must be called when the EnableDirectoryBrowsing property value is true.

Using the preceding file hierarchy and code, URLs resolve as follows:

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

If no default-named file exists in the MyStaticFiles directory, https://<hostname>/StaticFiles returns the directory listing with clickable links:

Static files list

UseDefaultFiles and UseDirectoryBrowser perform a client-side redirect from the target URI without a trailing / to the target URI with a trailing /. For example, from https://<hostname>/StaticFiles to https://<hostname>/StaticFiles/. Relative URLs within the StaticFiles directory are invalid without a trailing slash (/) unless the RedirectToAppendTrailingSlash option of DefaultFilesOptions is used.

FileExtensionContentTypeProvider

The FileExtensionContentTypeProvider class contains a Mappings property that serves as a mapping of file extensions to MIME content types. In the following sample, several file extensions are mapped to known MIME types. The .rtf extension is replaced, and .mp4 is removed:

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();

See MIME content types.

Non-standard content types

The Static File Middleware understands almost 400 known file content types. If the user requests a file with an unknown file type, the Static File Middleware passes the request to the next middleware in the pipeline. If no middleware handles the request, a 404 Not Found response is returned. If directory browsing is enabled, a link to the file is displayed in a directory listing.

The following code enables serving unknown types and renders the unknown file as an image:

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();

With the preceding code, a request for a file with an unknown content type is returned as an image.

Warning

Enabling ServeUnknownFileTypes is a security risk. It's disabled by default, and its use is discouraged. FileExtensionContentTypeProvider provides a safer alternative to serving files with non-standard extensions.

Serve files from multiple locations

Consider the following Razor page which displays the /MyStaticFiles/image3.png file:

@page

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

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

UseStaticFiles and UseFileServer default to the file provider pointing at wwwroot. Additional instances of UseStaticFiles and UseFileServer can be provided with other file providers to serve files from other locations. The following example calls UseStaticFiles twice to serve files from both wwwroot and MyStaticFiles:

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

Using the preceding code:

The following code updates the WebRootFileProvider, which enables the Image Tag Helper to provide a version:

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();

Security considerations for static files

Warning

UseDirectoryBrowser and UseStaticFiles can leak secrets. Disabling directory browsing in production is highly recommended. Carefully review which directories are enabled via UseStaticFiles or UseDirectoryBrowser. The entire directory and its sub-directories become publicly accessible. Store files suitable for serving to the public in a dedicated directory, such as <content_root>/wwwroot. Separate these files from MVC views, Razor Pages, configuration files, etc.

  • The URLs for content exposed with UseDirectoryBrowser and UseStaticFiles are subject to the case sensitivity and character restrictions of the underlying file system. For example, Windows is case insensitive, but macOS and Linux aren't.

  • ASP.NET Core apps hosted in IIS use the ASP.NET Core Module to forward all requests to the app, including static file requests. The IIS static file handler isn't used and has no chance to handle requests.

  • Complete the following steps in IIS Manager to remove the IIS static file handler at the server or website level:

    1. Navigate to the Modules feature.
    2. Select StaticFileModule in the list.
    3. Click Remove in the Actions sidebar.

Warning

If the IIS static file handler is enabled and the ASP.NET Core Module is configured incorrectly, static files are served. This happens, for example, if the web.config file isn't deployed.

  • Place code files, including .cs and .cshtml, outside of the app project's web root. A logical separation is therefore created between the app's client-side content and server-based code. This prevents server-side code from being leaked.

Serve files outside wwwroot by updating IWebHostEnvironment.WebRootPath

When IWebHostEnvironment.WebRootPath is set to a folder other than wwwroot:

  • In the development environment, static assets found in both wwwroot and the updated IWebHostEnvironment.WebRootPath are served from wwwroot.
  • In any environment other than development, duplicate static assets are served from the updated IWebHostEnvironment.WebRootPath folder.

Consider a web app created with the empty web template:

  • Containing an Index.html file in wwwroot and wwwroot-custom.

  • With the following updated Program.cs file that sets 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();
    

In the preceding code, requests to /:

  • In the development environment return wwwroot/Index.html
  • In any environment other than development return wwwroot-custom/Index.html

To ensure assets from wwwroot-custom are returned, use one of the following approaches:

  • Delete duplicate named assets in wwwroot.

  • Set "ASPNETCORE_ENVIRONMENT" in Properties/launchSettings.json to any value other than "Development".

  • Completely disable static web assets by setting <StaticWebAssetsEnabled>false</StaticWebAssetsEnabled> in the project file. WARNING, disabling static web assets disables Razor Class Libraries.

  • Add the following JSON to the project file:

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

The following code updates IWebHostEnvironment.WebRootPath to a non development value, guaranteeing duplicate content is returned from wwwroot-custom rather than 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();

Additional resources

By Rick Anderson and Kirk Larkin

Static files, such as HTML, CSS, images, and JavaScript, are assets an ASP.NET Core app serves directly to clients by default.

View or download sample code (how to download)

Serve static files

Static files are stored within the project's web root directory. The default directory is {content root}/wwwroot, but it can be changed with the UseWebRoot method. For more information, see Content root and Web root.

The CreateDefaultBuilder method sets the content root to the current directory:

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>();
            });
}

The preceding code was created with the web app template.

Static files are accessible via a path relative to the web root. For example, the Web Application project templates contain several folders within the wwwroot folder:

  • wwwroot
    • css
    • js
    • lib

Consider creating the wwwroot/images folder and adding the wwwroot/images/MyImage.jpg file. The URI format to access a file in the images folder is https://<hostname>/images/<image_file_name>. For example, https://localhost:5001/images/MyImage.jpg

Serve files in web root

The default web app templates call the UseStaticFiles method in Startup.Configure, which enables static files to be served:

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();
    });
}

The parameterless UseStaticFiles method overload marks the files in web root as servable. The following markup references wwwroot/images/MyImage.jpg:

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

In the preceding code, the tilde character ~/ points to the web root.

Serve files outside of web root

Consider a directory hierarchy in which the static files to be served reside outside of the web root:

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

A request can access the red-rose.jpg file by configuring the Static File Middleware as follows:

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();
    });
}

In the preceding code, the MyStaticFiles directory hierarchy is exposed publicly via the StaticFiles URI segment. A request to https://<hostname>/StaticFiles/images/red-rose.jpg serves the red-rose.jpg file.

The following markup references MyStaticFiles/images/red-rose.jpg:

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

Set HTTP response headers

A StaticFileOptions object can be used to set HTTP response headers. In addition to configuring static file serving from the web root, the following code sets the Cache-Control header:

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();
    });
}

The preceding code sets max-age to 604800 seconds (7 days).

Response headers showing the Cache-Control header has been added

Static file authorization

The ASP.NET Core templates call UseStaticFiles before calling UseAuthorization. Most apps follow this pattern. When the Static File Middleware is called before the authorization middleware:

  • No authorization checks are performed on the static files.
  • Static files served by the Static File Middleware, such as those under wwwroot, are publicly accessible.

To serve static files based on authorization:

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.

In the preceding code, the fallback authorization policy requires all users to be authenticated. Endpoints such as controllers, Razor Pages, etc that specify their own authorization requirements don't use the fallback authorization policy. For example, Razor Pages, controllers, or action methods with [AllowAnonymous] or [Authorize(PolicyName="MyPolicy")] use the applied authorization attribute rather than the fallback authorization policy.

RequireAuthenticatedUser adds DenyAnonymousAuthorizationRequirement to the current instance, which enforces that the current user is authenticated.

Static assets under wwwroot are publicly accessible because the default Static File Middleware (app.UseStaticFiles();) is called before UseAuthentication. Static assets in the MyStaticFiles folder require authentication. The sample code demonstrates this.

An alternative approach to serve files based on authorization is to:

  • Store them outside of wwwroot and any directory accessible to the Static File Middleware.
  • Serve them via an action method to which authorization is applied and return a FileResult object:
[Authorize]
public IActionResult BannerImage()
{
    var filePath = Path.Combine(
        _env.ContentRootPath, "MyStaticFiles", "images", "red-rose.jpg");

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

Directory browsing

Directory browsing allows directory listing within specified directories.

Directory browsing is disabled by default for security reasons. For more information, see Security considerations for static files.

Enable directory browsing with:

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();
    });
}

The preceding code allows directory browsing of the wwwroot/images folder using the URL https://<hostname>/MyImages, with links to each file and folder:

directory browsing

Serve default documents

Setting a default page provides visitors a starting point on a site. To serve a default file from wwwroot without requiring the request URL to include the file's name, call the UseDefaultFiles method:

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();
    });
}

UseDefaultFiles must be called before UseStaticFiles to serve the default file. UseDefaultFiles is a URL rewriter that doesn't serve the file.

With UseDefaultFiles, requests to a folder in wwwroot search for:

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

The first file found from the list is served as though the request included the file's name. The browser URL continues to reflect the URI requested.

The following code changes the default file name to mydefault.html:

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

The following code shows Startup.Configure with the preceding code:

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 for default documents

UseFileServer combines the functionality of UseStaticFiles, UseDefaultFiles, and optionally UseDirectoryBrowser.

Call app.UseFileServer to enable the serving of static files and the default file. Directory browsing isn't enabled. The following code shows Startup.Configure with 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();
    });
}

The following code enables the serving of static files, the default file, and directory browsing:

app.UseFileServer(enableDirectoryBrowsing: true);

The following code shows Startup.Configure with the preceding code:

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();
    });
}

Consider the following directory hierarchy:

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

The following code enables the serving of static files, the default file, and directory browsing of 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();
    });
}

AddDirectoryBrowser must be called when the EnableDirectoryBrowsing property value is true.

Using the file hierarchy and preceding code, URLs resolve as follows:

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

If no default-named file exists in the MyStaticFiles directory, https://<hostname>/StaticFiles returns the directory listing with clickable links:

Static files list

UseDefaultFiles and UseDirectoryBrowser perform a client-side redirect from the target URI without a trailing / to the target URI with a trailing /. For example, from https://<hostname>/StaticFiles to https://<hostname>/StaticFiles/. Relative URLs within the StaticFiles directory are invalid without a trailing slash (/).

FileExtensionContentTypeProvider

The FileExtensionContentTypeProvider class contains a Mappings property that serves as a mapping of file extensions to MIME content types. In the following sample, several file extensions are mapped to known MIME types. The .rtf extension is replaced, and .mp4 is removed:

// 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"
});

The following code shows Startup.Configure with the preceding code:

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();
    });
}

See MIME content types.

Non-standard content types

The Static File Middleware understands almost 400 known file content types. If the user requests a file with an unknown file type, the Static File Middleware passes the request to the next middleware in the pipeline. If no middleware handles the request, a 404 Not Found response is returned. If directory browsing is enabled, a link to the file is displayed in a directory listing.

The following code enables serving unknown types and renders the unknown file as an image:

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

The following code shows Startup.Configure with the preceding code:

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();
    });
}

With the preceding code, a request for a file with an unknown content type is returned as an image.

Warning

Enabling ServeUnknownFileTypes is a security risk. It's disabled by default, and its use is discouraged. FileExtensionContentTypeProvider provides a safer alternative to serving files with non-standard extensions.

Serve files from multiple locations

UseStaticFiles and UseFileServer default to the file provider pointing at wwwroot. Additional instances of UseStaticFiles and UseFileServer can be provided with other file providers to serve files from other locations. For more information, see this GitHub issue.

Security considerations for static files

Warning

UseDirectoryBrowser and UseStaticFiles can leak secrets. Disabling directory browsing in production is highly recommended. Carefully review which directories are enabled via UseStaticFiles or UseDirectoryBrowser. The entire directory and its sub-directories become publicly accessible. Store files suitable for serving to the public in a dedicated directory, such as <content_root>/wwwroot. Separate these files from MVC views, Razor Pages, configuration files, etc.

  • The URLs for content exposed with UseDirectoryBrowser and UseStaticFiles are subject to the case sensitivity and character restrictions of the underlying file system. For example, Windows is case insensitive, but macOS and Linux aren't.

  • ASP.NET Core apps hosted in IIS use the ASP.NET Core Module to forward all requests to the app, including static file requests. The IIS static file handler isn't used and has no chance to handle requests.

  • Complete the following steps in IIS Manager to remove the IIS static file handler at the server or website level:

    1. Navigate to the Modules feature.
    2. Select StaticFileModule in the list.
    3. Click Remove in the Actions sidebar.

Warning

If the IIS static file handler is enabled and the ASP.NET Core Module is configured incorrectly, static files are served. This happens, for example, if the web.config file isn't deployed.

  • Place code files, including .cs and .cshtml, outside of the app project's web root. A logical separation is therefore created between the app's client-side content and server-based code. This prevents server-side code from being leaked.

Additional resources