From 25b027cd23a9b7093286a2e988e303183421657f Mon Sep 17 00:00:00 2001 From: Rick Anderson Date: Wed, 1 Dec 2021 17:55:42 -1000 Subject: [PATCH] moniker prep for static files --- aspnetcore/fundamentals/static-files.md | 213 +++++++++++++----------- 1 file changed, 120 insertions(+), 93 deletions(-) diff --git a/aspnetcore/fundamentals/static-files.md b/aspnetcore/fundamentals/static-files.md index fec4a72ea3..803e94a0bb 100644 --- a/aspnetcore/fundamentals/static-files.md +++ b/aspnetcore/fundamentals/static-files.md @@ -2,16 +2,16 @@ title: Static files in ASP.NET Core author: rick-anderson description: Learn how to serve and secure static files and configure static file hosting middleware behaviors in an ASP.NET Core web app. -monikerRange: '>= aspnetcore-2.1' +monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 6/23/2020 +ms.date: 12/4/2021 no-loc: [Home, Privacy, Kestrel, appsettings.json, "ASP.NET Core Identity", cookie, Cookie, Blazor, "Identity", "Let's Encrypt", Razor, SignalR] uid: fundamentals/static-files --- # Static files in ASP.NET Core -::: moniker range=">= aspnetcore-3.0" +::: moniker range=">= aspnetcore-6.0" By [Rick Anderson](https://twitter.com/RickAndMSFT) and [Kirk Larkin](https://twitter.com/serpent5) @@ -269,46 +269,44 @@ With the preceding code, a request for a file with an unknown content type is re ::: moniker-end -::: moniker range="< aspnetcore-3.0" +::: moniker range="< aspnetcore-6.0" -By [Rick Anderson](https://twitter.com/RickAndMSFT) and [Scott Addie](https://twitter.com/Scott_Addie) +By [Rick Anderson](https://twitter.com/RickAndMSFT) and [Kirk Larkin](https://twitter.com/serpent5) -Static files, such as HTML, CSS, images, and JavaScript, are assets an ASP.NET Core app serves directly to clients. Some configuration is required to enable serving of these files. +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](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/fundamentals/static-files/samples) ([how to download](xref:index#how-to-download-a-sample)) ## Serve static files -Static files are stored within the project's [web root](xref:fundamentals/index#web-root) directory. The default directory is *{content root}/wwwroot*, but it can be changed via the method. See [Content root](xref:fundamentals/index#content-root) and [Web root](xref:fundamentals/index#web-root) for more information. +Static files are stored within the project's [web root](xref:fundamentals/index#web-root) directory. The default directory is `{content root}/wwwroot`, but it can be changed with the method. For more information, see [Content root](xref:fundamentals/index#content-root) and [Web root](xref:fundamentals/index#web-root). -The app's web host must be made aware of the content root directory. +The method sets the content root to the current directory: -The `WebHost.CreateDefaultBuilder` method sets the content root to the current directory: +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/Program2.cs?name=snippet_Program)] -[!code-csharp[](../common/samples/WebApplication1DotNetCore2.0App/Program.cs?name=snippet_Main&highlight=9)] +The preceding code was created with the web app template. -Static files are accessible via a path relative to the [web root](xref:fundamentals/index#web-root). For example, the **Web Application** project template contains several folders within the `wwwroot` folder: +Static files are accessible via a path relative to the [web root](xref:fundamentals/index#web-root). For example, the **Web Application** project templates contain several folders within the `wwwroot` folder: * `wwwroot` * `css` - * `images` * `js` + * `lib` -The URI format to access a file in the *images* subfolder is *http://\/images/\*. For example, *http://localhost:9189/images/banner3.svg*. +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:///images/`. For example, `https://localhost:5001/images/MyImage.jpg` -If targeting .NET Framework, add the [Microsoft.AspNetCore.StaticFiles](https://www.nuget.org/packages/Microsoft.AspNetCore.StaticFiles/) package to the project. If targeting .NET Core, the [Microsoft.AspNetCore.App metapackage](xref:fundamentals/metapackage-app) includes this package. +### Serve files in web root -Configure the [middleware](xref:fundamentals/middleware/index), which enables the serving of static files. +The default web app templates call the method in `Startup.Configure`, which enables static files to be served: -### Serve files inside of web root +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/Startup.cs?name=snippet_Configure&highlight=15)] -Invoke the method within `Startup.Configure`: +The parameterless `UseStaticFiles` method overload marks the files in [web root](xref:fundamentals/index#web-root) as servable. The following markup references *wwwroot/images/MyImage.jpg*: -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupStaticFiles.cs?name=snippet_ConfigureMethod&highlight=3)] - -The parameterless `UseStaticFiles` method overload marks the files in [web root](xref:fundamentals/index#web-root) as servable. The following markup references *wwwroot/images/banner1.svg*: - -[!code-cshtml[](static-files/samples/1.x/StaticFilesSample/Views/Home/Index.cshtml?name=snippet_static_file_wwwroot)] +```html +My image +``` In the preceding code, the tilde character `~/` points to the [web root](xref:fundamentals/index#web-root). @@ -322,148 +320,175 @@ Consider a directory hierarchy in which the static files to be served reside out * `js` * `MyStaticFiles` * `images` - * `banner1.svg` + * `red-rose.jpg` -A request can access the *banner1.svg* file by configuring the Static File Middleware as follows: +A request can access the `red-rose.jpg` file by configuring the Static File Middleware as follows: -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupTwoStaticFiles.cs?name=snippet_ConfigureMethod&highlight=5-10)] +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupRose.cs?name=snippet_Configure&highlight=15-22)] -In the preceding code, the *MyStaticFiles* directory hierarchy is exposed publicly via the *StaticFiles* URI segment. A request to *http://\/StaticFiles/images/banner1.svg* serves the *banner1.svg* file. +In the preceding code, the *MyStaticFiles* directory hierarchy is exposed publicly via the *StaticFiles* URI segment. A request to `https:///StaticFiles/images/red-rose.jpg` serves the *red-rose.jpg* file. -The following markup references *MyStaticFiles/images/banner1.svg*: +The following markup references *MyStaticFiles/images/red-rose.jpg*: -[!code-cshtml[](static-files/samples/1.x/StaticFilesSample/Views/Home/Index.cshtml?name=snippet_static_file_outside)] +```html +A red rose +``` ### Set HTTP response headers A object can be used to set HTTP response headers. In addition to configuring static file serving from the [web root](xref:fundamentals/index#web-root), the following code sets the `Cache-Control` header: -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupAddHeader.cs?name=snippet_ConfigureMethod)] -[!INCLUDE[about the series](~/includes/code-comments-loc.md)] +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupAddHeader.cs?name=snippet_Configure&highlight=15-24)] -The method exists in the [Microsoft.AspNetCore.Http](https://www.nuget.org/packages/Microsoft.AspNetCore.Http/) package. + -The files have been made publicly cacheable for 10 minutes (600 seconds) in the Development environment: +Static files are publicly cacheable for 600 seconds: ![Response headers showing the Cache-Control header has been added](static-files/_static/add-header.png) ## Static file authorization -The Static File Middleware doesn't provide authorization checks. Any files served by it, including those under *wwwroot*, are publicly accessible. To serve files based on authorization: +The ASP.NET Core templates call before calling . Most apps follow this pattern. When the Static File Middleware is called before the authorization middleware: -* 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. Return a object: + * 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: - [!code-csharp[](static-files/samples/1.x/StaticFilesSample/Controllers/HomeController.cs?name=snippet_BannerImageAction)] + * Store them outside of `wwwroot`. + * Call `UseStaticFiles`, specifying a path, after calling `UseAuthorization`. + * Set the [fallback authorization policy](xref:Microsoft.AspNetCore.Authorization.AuthorizationOptions.FallbackPolicy). -## Enable directory browsing + [!code-csharp[](static-files/samples/3.x/StaticFileAuth/Startup.cs?name=snippet2&highlight=24-29)] + + [!code-csharp[](static-files/samples/3.x/StaticFileAuth/Startup.cs?name=snippet1&highlight=20-25)] -Directory browsing allows users of your web app to see a directory listing and files within a specified directory. Directory browsing is disabled by default for security reasons (see [Considerations](#considerations)). Enable directory browsing by invoking the method in `Startup.Configure`: + 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. -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupBrowse.cs?name=snippet_ConfigureMethod&highlight=12-17)] + adds to the current instance, which enforces that the current user is authenticated. -Add required services by invoking the method from `Startup.ConfigureServices`: + 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](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/fundamentals/static-files/samples) demonstrates this. -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupBrowse.cs?name=snippet_ConfigureServicesMethod&highlight=3)] +An alternative approach to serve files based on authorization is to: -The preceding code allows directory browsing of the *wwwroot/images* folder using the URL *http://\/MyImages*, with links to each file and folder: + * 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 object: + + [!code-csharp[](static-files/samples/3.x/StaticFilesSample/Controllers/HomeController.cs?name=snippet_BannerImage)] + +## 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](#security-considerations-for-static-files). + +Enable directory browsing with: + +* in `Startup.ConfigureServices`. +* in `Startup.Configure`. + +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupBrowse.cs?name=snippet_ClassMembers&highlight=4,21-35)] + +The preceding code allows directory browsing of the *wwwroot/images* folder using the URL `https:///MyImages`, with links to each file and folder: ![directory browsing](static-files/_static/dir-browse.png) -See [Considerations](#considerations) on the security risks when enabling browsing. +## Serve default documents -Note the two `UseStaticFiles` calls in the following example. The first call enables the serving of static files in the *wwwroot* folder. The second call enables directory browsing of the *wwwroot/images* folder using the URL *http://\/MyImages*: +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 method: -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupBrowse.cs?name=snippet_ConfigureMethod&highlight=3,5)] +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupEmpty.cs?name=snippet_Configure&highlight=15)] -## Serve a default document +`UseDefaultFiles` must be called before `UseStaticFiles` to serve the default file. `UseDefaultFiles` is a URL rewriter that doesn't serve the file. -Setting a default home page provides visitors a logical starting point when visiting your site. To serve a default page without the user fully qualifying the URI, call the method from `Startup.Configure`: - -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupEmpty.cs?name=snippet_ConfigureMethod&highlight=3)] - -> [!IMPORTANT] -> `UseDefaultFiles` must be called before `UseStaticFiles` to serve the default file. `UseDefaultFiles` is a URL rewriter that doesn't actually serve the file. Enable Static File Middleware via `UseStaticFiles` to serve the file. - -With `UseDefaultFiles`, requests to a folder search for: +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 were the fully qualified URI. The browser URL continues to reflect the URI requested. +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*: -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupDefault.cs?name=snippet_ConfigureMethod)] +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupDefault.cs?name=snippet_DefaultFiles)] -## UseFileServer +The following code shows `Startup.Configure` with the preceding code: + +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupDefault.cs?name=snippet_Configure&highlight=15-19)] + +### UseFileServer for default documents combines the functionality of `UseStaticFiles`, `UseDefaultFiles`, and optionally `UseDirectoryBrowser`. -The following code enables the serving of static files and the default file. Directory browsing isn't enabled. +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`: -```csharp -app.UseFileServer(); -``` +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupEmpty2.cs?name=snippet_Configure&highlight=15)] -The following code builds upon the parameterless overload by enabling directory browsing: +The following code enables the serving of static files, the default file, and directory browsing: ```csharp app.UseFileServer(enableDirectoryBrowsing: true); ``` +The following code shows `Startup.Configure` with the preceding code: + +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupEmpty3.cs?name=snippet_Configure&highlight=15)] + Consider the following directory hierarchy: -* **wwwroot** - * **css** - * **images** - * **js** -* **MyStaticFiles** - * **images** - * *banner1.svg* - * *default.html* +* `wwwroot` + * `css` + * `images` + * `js` +* `MyStaticFiles` + * `images` + * `MyImage.jpg` + * `default.html` -The following code enables static files, default files, and directory browsing of `MyStaticFiles`: +The following code enables the serving of static files, the default file, and directory browsing of `MyStaticFiles`: -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupUseFileServer.cs?name=snippet_ConfigureMethod&highlight=5-11)] +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupFileServer.cs?name=snippet_ClassMembers&highlight=4,21-31)] -`AddDirectoryBrowser` must be called when the `EnableDirectoryBrowsing` property value is `true`: - -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupUseFileServer.cs?name=snippet_ConfigureServicesMethod)] + must be called when the `EnableDirectoryBrowsing` property value is `true`. Using the file hierarchy and preceding code, URLs resolve as follows: -| URI | Response | +| URI | Response | | ------- | ------| -| *http://\/StaticFiles/images/banner1.svg* | MyStaticFiles/images/banner1.svg | -| *http://\/StaticFiles* | MyStaticFiles/default.html | +| `https:///StaticFiles/images/MyImage.jpg` | *MyStaticFiles/images/MyImage.jpg* | +| `https:///StaticFiles` | *MyStaticFiles/default.html* | -If no default-named file exists in the *MyStaticFiles* directory, *http://\/StaticFiles* returns the directory listing with clickable links: +If no default-named file exists in the *MyStaticFiles* directory, `https:///StaticFiles` returns the directory listing with clickable links: ![Static files list](static-files/_static/db2.png) -> [!NOTE] -> and perform a client-side redirect from `http://{SERVER ADDRESS}/StaticFiles` (without a trailing slash) to `http://{SERVER ADDRESS}/StaticFiles/` (with a trailing slash). Relative URLs within the *StaticFiles* directory are invalid without a trailing slash. + and perform a client-side redirect from the target URI without a trailing `/` to the target URI with a trailing `/`. For example, from `https:///StaticFiles` to `https:///StaticFiles/`. Relative URLs within the *StaticFiles* directory are invalid without a trailing slash (`/`). ## FileExtensionContentTypeProvider -The class contains a `Mappings` property serving as a mapping of file extensions to MIME content types. In the following sample, several file extensions are registered to known MIME types. The *.rtf* extension is replaced, and *.mp4* is removed. +The 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: -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupFileExtensionContentTypeProvider.cs?name=snippet_ConfigureMethod&highlight=3-12,19)] +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupFileExtensionContentTypeProvider.cs?name=snippet_Provider)] + +The following code shows `Startup.Configure` with the preceding code: + +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupFileExtensionContentTypeProvider.cs?name=snippet_Configure&highlight=15-43)] See [MIME content types](https://www.iana.org/assignments/media-types/media-types.xhtml). -For information on using a custom or to configure other in Blazor Server apps, see . - ## Non-standard content types -Static File Middleware understands almost 400 known file content types. If the user requests a file with an unknown file type, 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 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: -[!code-csharp[](static-files/samples/1.x/StaticFilesSample/StartupServeUnknownFileTypes.cs?name=snippet_ConfigureMethod)] +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupServeUnknownFileTypes.cs?name=snippet_UseStaticFiles)] + +The following code shows `Startup.Configure` with the preceding code: + +[!code-csharp[](~/fundamentals/static-files/samples/3.x/StaticFilesSample/StartupServeUnknownFileTypes.cs?name=snippet_Configure&highlight=15-19)] With the preceding code, a request for a file with an unknown content type is returned as an image. @@ -472,16 +497,18 @@ With the preceding code, a request for a file with an unknown content type is re ## Serve files from multiple locations -`UseStaticFiles` and `UseFileServer` defaults to the file provider pointing at *wwwroot*. You can provide additional instances of `UseStaticFiles` and `UseFileServer` with other file providers to serve files from other locations. For more information, see [this GitHub issue](https://github.com/dotnet/AspNetCore.Docs/issues/15578). +`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](https://github.com/dotnet/AspNetCore.Docs/issues/15578). -### Considerations + + +### 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 *\/wwwroot*. Separate these files from MVC views, Razor Pages (2.x only), configuration files, etc. +> `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 `/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—macOS and Linux aren't. +* 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](xref:host-and-deploy/aspnet-core-module) to forward all requests to the app, including static file requests. The IIS static file handler isn't used. It has no chance to handle requests before they're handled by the module. +* ASP.NET Core apps hosted in IIS use the [ASP.NET Core Module](xref:host-and-deploy/aspnet-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. @@ -491,7 +518,7 @@ With the preceding code, a request for a file with an unknown content type is re > [!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](xref:fundamentals/index#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. +* Place code files, including *.cs* and *.cshtml*, outside of the app project's [web root](xref:fundamentals/index#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