diff --git a/src/Components/Blazor.sln b/src/Components/Blazor.sln index 8790973228..829fd645e4 100644 --- a/src/Components/Blazor.sln +++ b/src/Components/Blazor.sln @@ -552,9 +552,9 @@ Global {C4D74173-702B-428A-B689-1A9AF51CE356} = {B29FB58D-FAE5-405E-9695-BCF93582BE9A} {B3EF0C88-3466-40AE-9080-F694370F4192} = {C4D74173-702B-428A-B689-1A9AF51CE356} {2916EC17-1D1F-4949-9EC7-50725157F1A6} = {C4D74173-702B-428A-B689-1A9AF51CE356} - {7EFB9CAF-6716-43BF-A6EF-C2878E95F8A6} = {B4ACD900-27B6-482B-B434-2C1E86E9D8BC} - {194EBC45-F98E-4919-B714-C1624EF17B31} = {B4ACD900-27B6-482B-B434-2C1E86E9D8BC} - {EAF50654-98ED-44BB-A120-0436EC0CD3E0} = {B4ACD900-27B6-482B-B434-2C1E86E9D8BC} + {7EFB9CAF-6716-43BF-A6EF-C2878E95F8A6} = {CBD2BB24-3EC3-4950-ABE4-8C521D258DCD} + {194EBC45-F98E-4919-B714-C1624EF17B31} = {CBD2BB24-3EC3-4950-ABE4-8C521D258DCD} + {EAF50654-98ED-44BB-A120-0436EC0CD3E0} = {CBD2BB24-3EC3-4950-ABE4-8C521D258DCD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {27A36094-AA50-4FFD-ADE6-C055E391F741} diff --git a/src/Components/WebAssembly/DevServer/src/Server/Startup.cs b/src/Components/WebAssembly/DevServer/src/Server/Startup.cs index 9096edfb0f..84bf7ac852 100644 --- a/src/Components/WebAssembly/DevServer/src/Server/Startup.cs +++ b/src/Components/WebAssembly/DevServer/src/Server/Startup.cs @@ -28,8 +28,6 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server { services.AddRouting(); - services.AddWebAssemblyStaticFilesConfiguration(); - services.AddResponseCompression(options => { options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] @@ -54,6 +52,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server app.UseEndpoints(endpoints => { + endpoints.MapBlazorWebAssemblyApplication(); endpoints.MapFallbackToFile("index.html"); }); } diff --git a/src/Components/WebAssembly/Server/src/ComponentsWebAssemblyEndpointRouteBuilderExtensions.cs b/src/Components/WebAssembly/Server/src/ComponentsWebAssemblyEndpointRouteBuilderExtensions.cs new file mode 100644 index 0000000000..d29212ff75 --- /dev/null +++ b/src/Components/WebAssembly/Server/src/ComponentsWebAssemblyEndpointRouteBuilderExtensions.cs @@ -0,0 +1,111 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Mime; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Builder +{ + /// + /// Extensions for mapping Blazor WebAssembly applications. + /// + public static class ComponentsWebAssemblyEndpointRouteBuilderExtensions + { + /// + /// Maps a Blazor webassembly application to the . + /// + /// The . + /// The that indicates the prefix for the Blazor application. + /// The + public static IEndpointConventionBuilder MapBlazorWebAssemblyApplication(this IEndpointRouteBuilder endpoints, PathString pathPrefix) + { + if (endpoints is null) + { + throw new ArgumentNullException(nameof(endpoints)); + } + + var webHostEnvironment = endpoints.ServiceProvider.GetRequiredService(); + + var options = CreateStaticFilesOptions(webHostEnvironment.WebRootFileProvider); + var appBuilder = endpoints.CreateApplicationBuilder(); + + appBuilder.Use(async (ctx, next) => + { + var endpoint = ctx.GetEndpoint(); + try + { + // Set the endpoint to null so that static files doesn't discard the path. + ctx.SetEndpoint(null); + + if (ctx.Request.Path.StartsWithSegments(pathPrefix, out var rest) && + rest.StartsWithSegments("/_framework")) + { + // At this point we mapped something from the /_framework + ctx.Response.Headers.Append(HeaderNames.CacheControl, "no-cache"); + } + + // This will invoke the static files middleware plugged-in below. + await next(); + + } + finally + { + ctx.SetEndpoint(endpoint); + } + }); + + appBuilder.UseStaticFiles(options); + + var conventionBuilder = endpoints.Map( + $"{pathPrefix}/{{*path:file}}", + appBuilder.Build()); + + conventionBuilder.Add(builder => + { + // Map this route with low priority so that it doesn't interfere with any other potential request. + ((RouteEndpointBuilder)builder).Order = int.MaxValue - 100; + }); + + return conventionBuilder; + } + + /// + /// Maps a Blazor webassembly application to the root path of the application "/". + /// + /// The . + /// The that indicates the prefix for the Blazor application. + /// The + public static IEndpointConventionBuilder MapBlazorWebAssemblyApplication(this IEndpointRouteBuilder endpoints) => + MapBlazorWebAssemblyApplication(endpoints, default); + + private static StaticFileOptions CreateStaticFilesOptions(IFileProvider webRootFileProvider) + { + var options = new StaticFileOptions(); + options.FileProvider = webRootFileProvider; + var contentTypeProvider = new FileExtensionContentTypeProvider(); + AddMapping(contentTypeProvider, ".dll", MediaTypeNames.Application.Octet); + // We unconditionally map pdbs as there will be no pdbs in the output folder for + // release builds unless BlazorEnableDebugging is explicitly set to true. + AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet); + + options.ContentTypeProvider = contentTypeProvider; + + return options; + } + + private static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType) + { + if (!provider.Mappings.ContainsKey(name)) + { + provider.Mappings.Add(name, mimeType); + } + } + } +} diff --git a/src/Components/WebAssembly/Server/src/Services/ComponentsWebAssemblyServiceCollectionExtensions.cs b/src/Components/WebAssembly/Server/src/Services/ComponentsWebAssemblyServiceCollectionExtensions.cs deleted file mode 100644 index 07c95c063f..0000000000 --- a/src/Components/WebAssembly/Server/src/Services/ComponentsWebAssemblyServiceCollectionExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Net.Mime; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.StaticFiles; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class ComponentsWebAssemblyServiceCollectionExtensions - { - public static IServiceCollection AddWebAssemblyStaticFilesConfiguration(this IServiceCollection services) - { - services.TryAddEnumerable(ServiceDescriptor.Singleton, ClientSideBlazorStaticFilesConfiguration>()); - return services; - } - - private class ClientSideBlazorStaticFilesConfiguration : IConfigureOptions - { - private readonly IWebHostEnvironment _webHostEnvironment; - - public ClientSideBlazorStaticFilesConfiguration(IWebHostEnvironment webHostEnvironment) - { - _webHostEnvironment = webHostEnvironment; - } - - public void Configure(StaticFileOptions options) - { - options.FileProvider = _webHostEnvironment.WebRootFileProvider; - var contentTypeProvider = new FileExtensionContentTypeProvider(); - AddMapping(contentTypeProvider, ".dll", MediaTypeNames.Application.Octet); - // We unconditionally map pdbs as there will be no pdbs in the output folder for - // release builds unless BlazorEnableDebugging is explicitly set to true. - AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet); - - options.ContentTypeProvider = contentTypeProvider; - } - - private static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType) - { - if (!provider.Mappings.ContainsKey(name)) - { - provider.Mappings.Add(name, mimeType); - } - } - } - } -} diff --git a/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs index 40bdd6c37c..a605d10ded 100644 --- a/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs @@ -15,7 +15,6 @@ namespace HostedInAspNet.Server public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); - services.AddWebAssemblyStaticFilesConfiguration(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -34,12 +33,11 @@ namespace HostedInAspNet.Server app.UseBlazorDebugging(); } - app.UseStaticFiles(); - app.UseRouting(); app.UseEndpoints(endpoints => { + endpoints.MapBlazorWebAssemblyApplication(); endpoints.MapFallbackToFile("index.html"); }); } diff --git a/src/Components/WebAssembly/testassets/MonoSanity/Startup.cs b/src/Components/WebAssembly/testassets/MonoSanity/Startup.cs index 17f115a565..f777cbc4b3 100644 --- a/src/Components/WebAssembly/testassets/MonoSanity/Startup.cs +++ b/src/Components/WebAssembly/testassets/MonoSanity/Startup.cs @@ -10,7 +10,6 @@ namespace MonoSanity { public void ConfigureServices(IServiceCollection services) { - services.AddWebAssemblyStaticFilesConfiguration(); } public void Configure(IApplicationBuilder app) @@ -21,6 +20,7 @@ namespace MonoSanity app.UseRouting(); app.UseEndpoints(endpoints => { + endpoints.MapBlazorWebAssemblyApplication(); endpoints.MapFallbackToFile("index.html"); }); } diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs index 2e239f07a0..58c8fdb2c1 100644 --- a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs @@ -37,8 +37,6 @@ namespace Wasm.Authentication.Server services.AddAuthentication() .AddIdentityServerJwt(); - services.AddWebAssemblyStaticFilesConfiguration(); - services.AddMvc(); services.AddResponseCompression(opts => { @@ -58,8 +56,6 @@ namespace Wasm.Authentication.Server app.UseBlazorDebugging(); } - app.UseStaticFiles(); - app.UseRouting(); app.UseAuthentication(); @@ -70,6 +66,8 @@ namespace Wasm.Authentication.Server { endpoints.MapControllers(); endpoints.MapRazorPages(); + + endpoints.MapBlazorWebAssemblyApplication(); endpoints.MapFallbackToFile("index.html"); }); } diff --git a/src/Components/test/E2ETest/Tests/BootResourceCachingTest.cs b/src/Components/test/E2ETest/Tests/BootResourceCachingTest.cs index 941a99c253..7452c30a5f 100644 --- a/src/Components/test/E2ETest/Tests/BootResourceCachingTest.cs +++ b/src/Components/test/E2ETest/Tests/BootResourceCachingTest.cs @@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests var subsequentResourcesRequested = GetAndClearRequestedPaths(); Assert.NotEmpty(initialResourcesRequested.Where(path => path.EndsWith("/blazor.boot.json"))); Assert.Empty(subsequentResourcesRequested.Where(path => path.EndsWith("/dotnet.wasm"))); - Assert.Empty(subsequentResourcesRequested.Where(path => path.EndsWith(".js"))); + Assert.NotEmpty(subsequentResourcesRequested.Where(path => path.EndsWith(".js"))); Assert.Empty(subsequentResourcesRequested.Where(path => path.EndsWith(".dll"))); } diff --git a/src/Components/test/testassets/TestServer/AuthenticationStartup.cs b/src/Components/test/testassets/TestServer/AuthenticationStartup.cs index a395dad910..261587c9ee 100644 --- a/src/Components/test/testassets/TestServer/AuthenticationStartup.cs +++ b/src/Components/test/testassets/TestServer/AuthenticationStartup.cs @@ -28,8 +28,6 @@ namespace TestServer services.AddServerSideBlazor(); - services.AddWebAssemblyStaticFilesConfiguration(); - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); services.AddAuthorization(options => { @@ -56,6 +54,7 @@ namespace TestServer app.UseRouting(); app.UseEndpoints(endpoints => { + endpoints.MapBlazorWebAssemblyApplication(); endpoints.MapControllers(); endpoints.MapRazorPages(); endpoints.MapBlazorHub(); diff --git a/src/Components/test/testassets/TestServer/ClientStartup.cs b/src/Components/test/testassets/TestServer/ClientStartup.cs index 6381adc921..4d4161b13d 100644 --- a/src/Components/test/testassets/TestServer/ClientStartup.cs +++ b/src/Components/test/testassets/TestServer/ClientStartup.cs @@ -23,7 +23,6 @@ namespace TestServer { services.AddMvc(); services.AddServerSideBlazor(); - services.AddWebAssemblyStaticFilesConfiguration(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -43,6 +42,8 @@ namespace TestServer app.UseRouting(); app.UseEndpoints(endpoints => { + endpoints.MapBlazorWebAssemblyApplication(); + endpoints.MapRazorPages(); endpoints.MapControllers(); endpoints.MapFallbackToFile("index.html"); diff --git a/src/Components/test/testassets/TestServer/CorsStartup.cs b/src/Components/test/testassets/TestServer/CorsStartup.cs index 0ae0a13c8a..c6ed533955 100644 --- a/src/Components/test/testassets/TestServer/CorsStartup.cs +++ b/src/Components/test/testassets/TestServer/CorsStartup.cs @@ -19,7 +19,6 @@ namespace TestServer public void ConfigureServices(IServiceCollection services) { services.AddMvc(); - services.AddWebAssemblyStaticFilesConfiguration(); services.AddCors(options => { // It's not enough just to return "Access-Control-Allow-Origin: *", because @@ -54,6 +53,8 @@ namespace TestServer app.UseEndpoints(endpoints => { + endpoints.MapBlazorWebAssemblyApplication(); + endpoints.MapControllers(); endpoints.MapFallbackToFile("index.html"); }); diff --git a/src/Components/test/testassets/TestServer/InternationalizationStartup.cs b/src/Components/test/testassets/TestServer/InternationalizationStartup.cs index 752778f481..5f19ea80f5 100644 --- a/src/Components/test/testassets/TestServer/InternationalizationStartup.cs +++ b/src/Components/test/testassets/TestServer/InternationalizationStartup.cs @@ -23,7 +23,6 @@ namespace TestServer { services.AddMvc(); services.AddServerSideBlazor(); - services.AddWebAssemblyStaticFilesConfiguration(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -55,6 +54,8 @@ namespace TestServer app.UseRouting(); app.UseEndpoints(endpoints => { + endpoints.MapBlazorWebAssemblyApplication(); + endpoints.MapControllers(); endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_ServerHost"); diff --git a/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs b/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs index 1b30089b16..9097914793 100644 --- a/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs +++ b/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs @@ -21,7 +21,6 @@ namespace TestServer public void ConfigureServices(IServiceCollection services) { - services.AddWebAssemblyStaticFilesConfiguration(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) @@ -34,7 +33,12 @@ namespace TestServer // The client-side files middleware needs to be here because the base href in hardcoded to /subdir/ app.Map("/subdir", app => { - app.UseStaticFiles(); + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapBlazorWebAssemblyApplication(); + }); }); // The calls to `Map` allow us to test each of these overloads, while keeping them isolated. diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs index 1e2ccb8c5d..85573e7daf 100644 --- a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs @@ -71,8 +71,6 @@ namespace ComponentsWebAssembly_CSharp.Server services.AddRazorPages(); #endif - services.AddWebAssemblyStaticFilesConfiguration(); - services.AddResponseCompression(opts => { opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat( @@ -127,6 +125,7 @@ namespace ComponentsWebAssembly_CSharp.Server #endif endpoints.MapControllers(); + endpoints.MapBlazorWebAssemblyApplication(); endpoints.MapFallbackToFile("index.html"); }); }