Skip to content

Commit d3400f7

Browse files
authored
Fix: dotnet#6882 - Explode UseBlazor (dotnet#9449)
This changes the recipe for client-side blazor to use similar primitives to server side applications. --- I ignored auto-rebuild support because it's currently dead code until we have that in VS. If we add auto-rebuild to ASP.NET Core - we'd probably want to make that a separate gesture inside `IsDevelopement()` like other dev-time features anyway. --- The static files hookup is a special thing because creating the file server for a client-side Blazor app involves some non-trivial work. We plan to make this better in the future. What's nice about this pattern is that the implementation is pretty simple and literal, and it scales fine if you have multiple Blazor client-side apps. I didn't provide a lot of options here, it's pretty much the same as UseBlazor. --- I feel pretty good about the wireup with routing to use the `index.html` from the client app. I think it's pretty to-the-point.
1 parent 8fd86c3 commit d3400f7

File tree

17 files changed

+549
-210
lines changed

17 files changed

+549
-210
lines changed

src/Components/Blazor/DevServer/src/Server/Startup.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internal class Startup
2020
public void ConfigureServices(IServiceCollection services)
2121
{
2222
services.AddRouting();
23+
2324
services.AddResponseCompression(options =>
2425
{
2526
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
@@ -30,15 +31,22 @@ public void ConfigureServices(IServiceCollection services)
3031
});
3132
}
3233

33-
public void Configure(IApplicationBuilder app, IConfiguration configuration)
34+
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, IConfiguration configuration)
3435
{
3536
app.UseDeveloperExceptionPage();
3637
app.UseResponseCompression();
3738
EnableConfiguredPathbase(app, configuration);
3839

39-
var clientAssemblyPath = FindClientAssemblyPath(app);
40-
app.UseBlazor(new BlazorOptions { ClientAssemblyPath = clientAssemblyPath });
4140
app.UseBlazorDebugging();
41+
42+
app.UseClientSideBlazorFiles(FindClientAssemblyPath(environment));
43+
44+
app.UseRouting();
45+
46+
app.UseEndpoints(endpoints =>
47+
{
48+
endpoints.MapFallbackToClientSideBlazor(FindClientAssemblyPath(environment), "index.html");
49+
});
4250
}
4351

4452
private static void EnableConfiguredPathbase(IApplicationBuilder app, IConfiguration configuration)
@@ -66,10 +74,9 @@ private static void EnableConfiguredPathbase(IApplicationBuilder app, IConfigura
6674
}
6775
}
6876

69-
private static string FindClientAssemblyPath(IApplicationBuilder app)
77+
private static string FindClientAssemblyPath(IWebHostEnvironment environment)
7078
{
71-
var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
72-
var contentRoot = env.ContentRootPath;
79+
var contentRoot = environment.ContentRootPath;
7380
var binDir = FindClientBinDir(contentRoot);
7481
var appName = Path.GetFileName(contentRoot); // TODO: Allow for the possibility that the assembly name has been overridden
7582
var assemblyPath = Path.Combine(binDir, $"{appName}.dll");

src/Components/Blazor/Server/ref/Microsoft.AspNetCore.Blazor.Server.netcoreapp3.0.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33

44
namespace Microsoft.AspNetCore.Builder
55
{
6-
public static partial class BlazorApplicationBuilderExtensions
6+
public static partial class BlazorHostingApplicationBuilderExtensions
77
{
8-
public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseBlazor(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, Microsoft.AspNetCore.Builder.BlazorOptions options) { throw null; }
9-
public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseBlazor<TProgram>(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; }
8+
public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseClientSideBlazorFiles(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, string clientAssemblyFilePath) { throw null; }
9+
public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseClientSideBlazorFiles<TClientApp>(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; }
1010
}
11-
public static partial class BlazorMonoDebugProxyAppBuilderExtensions
11+
public static partial class BlazorHostingEndpointRouteBuilderExtensions
1212
{
13-
public static void UseBlazorDebugging(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { }
13+
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToClientSideBlazor(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string filePath) { throw null; }
14+
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToClientSideBlazor(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string pattern, string filePath) { throw null; }
15+
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string filePath) { throw null; }
16+
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, string filePath) { throw null; }
1417
}
15-
public partial class BlazorOptions
18+
public static partial class BlazorMonoDebugProxyAppBuilderExtensions
1619
{
17-
public BlazorOptions() { }
18-
public string ClientAssemblyPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
20+
public static void UseBlazorDebugging(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { }
1921
}
2022
}

src/Components/Blazor/Server/src/BlazorConfig.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,32 @@ private BlazorConfig(string assemblyPath)
4747
EnableAutoRebuilding = configLines.Contains("autorebuild:true", StringComparer.Ordinal);
4848
EnableDebugging = configLines.Contains("debug:true", StringComparer.Ordinal);
4949
}
50+
51+
public string FindIndexHtmlFile()
52+
{
53+
// Before publishing, the client project may have a wwwroot directory.
54+
// If so, and if it contains index.html, use that.
55+
if (!string.IsNullOrEmpty(WebRootPath))
56+
{
57+
var wwwrootIndexHtmlPath = Path.Combine(WebRootPath, "index.html");
58+
if (File.Exists(wwwrootIndexHtmlPath))
59+
{
60+
return wwwrootIndexHtmlPath;
61+
}
62+
}
63+
64+
// After publishing, the client project won't have a wwwroot directory.
65+
// The contents from that dir will have been copied to "dist" during publish.
66+
// So if "dist/index.html" now exists, use that.
67+
var distIndexHtmlPath = Path.Combine(DistPath, "index.html");
68+
if (File.Exists(distIndexHtmlPath))
69+
{
70+
return distIndexHtmlPath;
71+
}
72+
73+
// Since there's no index.html, we'll use the default DefaultPageStaticFileOptions,
74+
// hence we'll look for index.html in the host server app's wwwroot.
75+
return null;
76+
}
5077
}
5178
}

src/Components/Blazor/Server/src/Builder/BlazorApplicationBuilderExtensions.cs

Lines changed: 0 additions & 162 deletions
This file was deleted.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Net.Mime;
7+
using Microsoft.AspNetCore.Blazor.Server;
8+
using Microsoft.AspNetCore.StaticFiles;
9+
using Microsoft.Extensions.FileProviders;
10+
11+
namespace Microsoft.AspNetCore.Builder
12+
{
13+
/// <summary>
14+
/// Provides extension methods for hosting client-side Blazor applications in ASP.NET Core.
15+
/// </summary>
16+
public static class BlazorHostingApplicationBuilderExtensions
17+
{
18+
/// <summary>
19+
/// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application
20+
/// specified by <typeparamref name="TClientApp"/>.
21+
/// </summary>
22+
/// <typeparam name="TClientApp">A type in the client-side application.</typeparam>
23+
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
24+
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
25+
public static IApplicationBuilder UseClientSideBlazorFiles<TClientApp>(this IApplicationBuilder app)
26+
{
27+
if (app == null)
28+
{
29+
throw new ArgumentNullException(nameof(app));
30+
}
31+
32+
UseClientSideBlazorFiles(app, typeof(TClientApp).Assembly.Location);
33+
return app;
34+
}
35+
36+
/// <summary>
37+
/// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application
38+
/// specified by <paramref name="clientAssemblyFilePath"/>.
39+
/// </summary>
40+
/// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param>
41+
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
42+
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
43+
public static IApplicationBuilder UseClientSideBlazorFiles(this IApplicationBuilder app, string clientAssemblyFilePath)
44+
{
45+
if (clientAssemblyFilePath == null)
46+
{
47+
throw new ArgumentNullException(nameof(clientAssemblyFilePath));
48+
}
49+
50+
var fileProviders = new List<IFileProvider>();
51+
52+
// TODO: Make the .blazor.config file contents sane
53+
// Currently the items in it are bizarre and don't relate to their purpose,
54+
// hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either.
55+
var config = BlazorConfig.Read(clientAssemblyFilePath);
56+
57+
// First, match the request against files in the client app dist directory
58+
fileProviders.Add(new PhysicalFileProvider(config.DistPath));
59+
60+
// * Before publishing, we serve the wwwroot files directly from source
61+
// (and don't require them to be copied into dist).
62+
// In this case, WebRootPath will be nonempty if that directory exists.
63+
// * After publishing, the wwwroot files are already copied to 'dist' and
64+
// will be served by the above middleware, so we do nothing here.
65+
// In this case, WebRootPath will be empty (the publish process sets this).
66+
if (!string.IsNullOrEmpty(config.WebRootPath))
67+
{
68+
fileProviders.Add(new PhysicalFileProvider(config.WebRootPath));
69+
}
70+
71+
// We can't modify an IFileContentTypeProvider, so we have to decorate.
72+
var contentTypeProvider = new FileExtensionContentTypeProvider();
73+
AddMapping(contentTypeProvider, ".dll", MediaTypeNames.Application.Octet);
74+
if (config.EnableDebugging)
75+
{
76+
AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet);
77+
}
78+
79+
var options = new StaticFileOptions()
80+
{
81+
ContentTypeProvider = contentTypeProvider,
82+
FileProvider = new CompositeFileProvider(fileProviders),
83+
OnPrepareResponse = CacheHeaderSettings.SetCacheHeaders,
84+
};
85+
86+
app.UseStaticFiles(options);
87+
return app;
88+
89+
static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType)
90+
{
91+
if (!provider.Mappings.ContainsKey(name))
92+
{
93+
provider.Mappings.Add(name, mimeType);
94+
}
95+
}
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)