-
Notifications
You must be signed in to change notification settings - Fork 9.8k
/
ComponentsWebAssemblyApplicationBuilderExtensions.cs
141 lines (123 loc) · 7.28 KB
/
ComponentsWebAssemblyApplicationBuilderExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using System.Linq;
using System.Net.Mime;
using Microsoft.AspNetCore.Components.WebAssembly.Server;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Extensions for mapping Blazor WebAssembly applications.
/// </summary>
public static class ComponentsWebAssemblyApplicationBuilderExtensions
{
/// <summary>
/// Configures the application to serve Blazor WebAssembly framework files from the path <paramref name="pathPrefix"/>. This path must correspond to a referenced Blazor WebAssembly application project.
/// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="pathPrefix">The <see cref="PathString"/> that indicates the prefix for the Blazor WebAssembly application.</param>
/// <returns>The <see cref="IApplicationBuilder"/></returns>
public static IApplicationBuilder UseBlazorFrameworkFiles(this IApplicationBuilder builder, PathString pathPrefix)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
var webHostEnvironment = builder.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
var options = CreateStaticFilesOptions(webHostEnvironment.WebRootFileProvider);
builder.MapWhen(ctx => ctx.Request.Path.StartsWithSegments(pathPrefix, out var rest) && rest.StartsWithSegments("/_framework") &&
!rest.StartsWithSegments("/_framework/blazor.server.js"),
subBuilder =>
{
subBuilder.Use(async (context, next) =>
{
context.Response.Headers.Append("Blazor-Environment", webHostEnvironment.EnvironmentName);
if (webHostEnvironment.IsDevelopment())
{
// DOTNET_MODIFIABLE_ASSEMBLIES is used by the runtime to initialize hot-reload specific environment variables and is configured
// by the launching process (dotnet-watch / Visual Studio).
// In Development, we'll transmit the environment variable to WebAssembly as a HTTP header. The bootstrapping code will read the header
// and configure it as env variable for the wasm app.
if (Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES") is string dotnetModifiableAssemblies)
{
context.Response.Headers.Append("DOTNET-MODIFIABLE-ASSEMBLIES", dotnetModifiableAssemblies);
}
// See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000
// Translate the _ASPNETCORE_BROWSER_TOOLS environment configured by the browser tools agent in to a HTTP response header.
if (Environment.GetEnvironmentVariable("__ASPNETCORE_BROWSER_TOOLS") is string blazorWasmHotReload)
{
context.Response.Headers.Append("ASPNETCORE-BROWSER-TOOLS", blazorWasmHotReload);
}
}
await next(context);
});
subBuilder.UseMiddleware<ContentEncodingNegotiator>();
subBuilder.UseStaticFiles(options);
});
return builder;
}
/// <summary>
/// Configures the application to serve Blazor WebAssembly framework files from the root path "/".
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
/// <returns>The <see cref="IApplicationBuilder"/></returns>
public static IApplicationBuilder UseBlazorFrameworkFiles(this IApplicationBuilder applicationBuilder) =>
UseBlazorFrameworkFiles(applicationBuilder, 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);
AddMapping(contentTypeProvider, ".br", MediaTypeNames.Application.Octet);
AddMapping(contentTypeProvider, ".dat", MediaTypeNames.Application.Octet);
AddMapping(contentTypeProvider, ".blat", MediaTypeNames.Application.Octet);
options.ContentTypeProvider = contentTypeProvider;
// Static files middleware will try to use application/x-gzip as the content
// type when serving a file with a gz extension. We need to correct that before
// sending the file.
options.OnPrepareResponse = fileContext =>
{
// At this point we mapped something from the /_framework
fileContext.Context.Response.Headers.Append(HeaderNames.CacheControl, "no-cache");
var requestPath = fileContext.Context.Request.Path;
var fileExtension = Path.GetExtension(requestPath.Value);
if (string.Equals(fileExtension, ".gz") || string.Equals(fileExtension, ".br"))
{
// When we are serving framework files (under _framework/ we perform content negotiation
// on the accept encoding and replace the path with <<original>>.gz|br if we can serve gzip or brotli content
// respectively.
// Here we simply calculate the original content type by removing the extension and apply it
// again.
// When we revisit this, we should consider calculating the original content type and storing it
// in the request along with the original target path so that we don't have to calculate it here.
var originalPath = Path.GetFileNameWithoutExtension(requestPath.Value);
if (originalPath != null && contentTypeProvider.TryGetContentType(originalPath, out var originalContentType))
{
fileContext.Context.Response.ContentType = originalContentType;
}
}
};
return options;
}
private static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType)
{
if (!provider.Mappings.ContainsKey(name))
{
provider.Mappings.Add(name, mimeType);
}
}
}
}