-
Notifications
You must be signed in to change notification settings - Fork 9.8k
/
SpaStaticFilesExtensions.cs
134 lines (121 loc) · 5.79 KB
/
SpaStaticFilesExtensions.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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.SpaServices.StaticFiles;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extension methods for configuring an application to serve static files for a
/// Single Page Application (SPA).
/// </summary>
public static class SpaStaticFilesExtensions
{
/// <summary>
/// Registers an <see cref="ISpaStaticFileProvider"/> service that can provide static
/// files to be served for a Single Page Application (SPA).
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configuration">If specified, this callback will be invoked to set additional configuration options.</param>
public static void AddSpaStaticFiles(
this IServiceCollection services,
Action<SpaStaticFilesOptions>? configuration = null)
{
services.AddSingleton<ISpaStaticFileProvider>(serviceProvider =>
{
// Use the options configured in DI (or blank if none was configured)
var optionsProvider = serviceProvider.GetService<IOptions<SpaStaticFilesOptions>>()!;
var options = optionsProvider.Value;
// Allow the developer to perform further configuration
configuration?.Invoke(options);
if (string.IsNullOrEmpty(options.RootPath))
{
throw new InvalidOperationException($"No {nameof(SpaStaticFilesOptions.RootPath)} " +
$"was set on the {nameof(SpaStaticFilesOptions)}.");
}
return new DefaultSpaStaticFileProvider(serviceProvider, options);
});
}
/// <summary>
/// Configures the application to serve static files for a Single Page Application (SPA).
/// The files will be located using the registered <see cref="ISpaStaticFileProvider"/> service.
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
public static void UseSpaStaticFiles(this IApplicationBuilder applicationBuilder)
{
UseSpaStaticFiles(applicationBuilder, new StaticFileOptions());
}
/// <summary>
/// Configures the application to serve static files for a Single Page Application (SPA).
/// The files will be located using the registered <see cref="ISpaStaticFileProvider"/> service.
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="options">Specifies options for serving the static files.</param>
public static void UseSpaStaticFiles(this IApplicationBuilder applicationBuilder, StaticFileOptions options)
{
ArgumentNullException.ThrowIfNull(applicationBuilder);
ArgumentNullException.ThrowIfNull(options);
UseSpaStaticFilesInternal(applicationBuilder,
staticFileOptions: options,
allowFallbackOnServingWebRootFiles: false);
}
internal static void UseSpaStaticFilesInternal(
this IApplicationBuilder app,
StaticFileOptions staticFileOptions,
bool allowFallbackOnServingWebRootFiles)
{
ArgumentNullException.ThrowIfNull(staticFileOptions);
// If the file provider was explicitly supplied, that takes precedence over any other
// configured file provider. This is most useful if the application hosts multiple SPAs
// (via multiple calls to UseSpa()), so each needs to serve its own separate static files
// instead of using AddSpaStaticFiles/UseSpaStaticFiles.
// But if no file provider was specified, try to get one from the DI config.
if (staticFileOptions.FileProvider == null)
{
var shouldServeStaticFiles = ShouldServeStaticFiles(
app,
allowFallbackOnServingWebRootFiles,
out var fileProviderOrDefault);
if (shouldServeStaticFiles)
{
staticFileOptions.FileProvider = fileProviderOrDefault;
}
else
{
// The registered ISpaStaticFileProvider says we shouldn't
// serve static files
return;
}
}
app.UseStaticFiles(staticFileOptions);
}
private static bool ShouldServeStaticFiles(
IApplicationBuilder app,
bool allowFallbackOnServingWebRootFiles,
out IFileProvider? fileProviderOrDefault)
{
var spaStaticFilesService = app.ApplicationServices.GetService<ISpaStaticFileProvider>();
if (spaStaticFilesService != null)
{
// If an ISpaStaticFileProvider was configured but it says no IFileProvider is available
// (i.e., it supplies 'null'), this implies we should not serve any static files. This
// is typically the case in development when SPA static files are being served from a
// SPA development server (e.g., Angular CLI or create-react-app), in which case no
// directory of prebuilt files will exist on disk.
fileProviderOrDefault = spaStaticFilesService.FileProvider;
return fileProviderOrDefault != null;
}
else if (!allowFallbackOnServingWebRootFiles)
{
throw new InvalidOperationException($"To use {nameof(UseSpaStaticFiles)}, you must " +
$"first register an {nameof(ISpaStaticFileProvider)} in the service provider, typically " +
$"by calling services.{nameof(AddSpaStaticFiles)}.");
}
else
{
// Fall back on serving wwwroot
fileProviderOrDefault = null;
return true;
}
}
}