Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 30333e2

Browse filesBrowse files
AddSpaStaticFiles/UseSpaStaticFiles APIs to clean up the React template (or other cases where SPA files are outside wwwroot)
1 parent 08002e9 commit 30333e2
Copy full SHA for 30333e2

File tree

Expand file treeCollapse file tree

7 files changed

+229
-5
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+229
-5
lines changed
Open diff view settings
Collapse file

‎build/dependencies.props‎

Copy file name to clipboardExpand all lines: build/dependencies.props
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreStaticFilesPackageVersion>
1616
<MicrosoftAspNetCoreWebSocketsPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreWebSocketsPackageVersion>
1717
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsDependencyInjectionPackageVersion>
18+
<MicrosoftExtensionsFileProvidersPhysicalPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsFileProvidersPhysicalPackageVersion>
1819
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsLoggingConsolePackageVersion>
1920
<MicrosoftExtensionsLoggingDebugPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsLoggingDebugPackageVersion>
2021
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
Collapse file

‎src/Microsoft.AspNetCore.SpaServices.Extensions/Microsoft.AspNetCore.SpaServices.Extensions.csproj‎

Copy file name to clipboardExpand all lines: src/Microsoft.AspNetCore.SpaServices.Extensions/Microsoft.AspNetCore.SpaServices.Extensions.csproj
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<ItemGroup>
1313
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
1414
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="$(MicrosoftAspNetCoreWebSocketsPackageVersion)" />
15+
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="$(MicrosoftExtensionsFileProvidersPhysicalPackageVersion)" />
1516
</ItemGroup>
1617

1718
</Project>
Collapse file

‎src/Microsoft.AspNetCore.SpaServices.Extensions/SpaDefaultPageMiddleware.cs‎

Copy file name to clipboardExpand all lines: src/Microsoft.AspNetCore.SpaServices.Extensions/SpaDefaultPageMiddleware.cs
+7-5Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Microsoft.AspNetCore.Builder;
55
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.Extensions.DependencyInjection;
67
using System;
78

89
namespace Microsoft.AspNetCore.SpaServices
@@ -26,11 +27,12 @@ public static void Attach(ISpaBuilder spaBuilder)
2627
return next();
2728
});
2829

29-
// Serve it as file from wwwroot (by default), or any other configured file provider
30-
app.UseStaticFiles(new StaticFileOptions
31-
{
32-
FileProvider = options.DefaultPageFileProvider
33-
});
30+
// Serve it as a static file
31+
// Developers who need to host more than one SPA with distinct default pages can
32+
// override the file provider
33+
app.UseSpaStaticFilesInternal(
34+
overrideFileProvider: options.DefaultPageFileProvider,
35+
allowFallbackOnServingWebRootFiles: true);
3436

3537
// If the default file didn't get served as a static file (usually because it was not
3638
// present on disk), the SPA is definitely not going to work.
Collapse file
+52Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 Microsoft.AspNetCore.Hosting;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.FileProviders;
7+
using System;
8+
using System.IO;
9+
10+
namespace Microsoft.AspNetCore.SpaServices.StaticFiles
11+
{
12+
/// <summary>
13+
/// Provides an implementation of <see cref="ISpaStaticFileProvider"/> that supplies
14+
/// physical files at a location configured using <see cref="SpaStaticFilesOptions"/>.
15+
/// </summary>
16+
internal class DefaultSpaStaticFileProvider : ISpaStaticFileProvider
17+
{
18+
private IFileProvider _fileProvider;
19+
20+
public DefaultSpaStaticFileProvider(
21+
IServiceProvider serviceProvider,
22+
SpaStaticFilesOptions options)
23+
{
24+
if (options == null)
25+
{
26+
throw new ArgumentNullException(nameof(options));
27+
}
28+
29+
if (string.IsNullOrEmpty(options.RootPath))
30+
{
31+
throw new ArgumentException($"The {nameof(options.RootPath)} property " +
32+
$"of {nameof(options)} cannot be null or empty.");
33+
}
34+
35+
var env = serviceProvider.GetRequiredService<IHostingEnvironment>();
36+
var absoluteRootPath = Path.Combine(
37+
env.ContentRootPath,
38+
options.RootPath);
39+
40+
// PhysicalFileProvider will throw if you pass a non-existent path,
41+
// but we don't want that scenario to be an error because for SPA
42+
// scenarios, it's better if non-existing directory just means we
43+
// don't serve any static files.
44+
if (Directory.Exists(absoluteRootPath))
45+
{
46+
_fileProvider = new PhysicalFileProvider(absoluteRootPath);
47+
}
48+
}
49+
50+
public IFileProvider FileProvider => _fileProvider;
51+
}
52+
}
Collapse file
+20Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 Microsoft.Extensions.FileProviders;
5+
6+
namespace Microsoft.AspNetCore.SpaServices.StaticFiles
7+
{
8+
/// <summary>
9+
/// Represents a service that can provide static files to be served for a Single Page
10+
/// Application (SPA).
11+
/// </summary>
12+
public interface ISpaStaticFileProvider
13+
{
14+
/// <summary>
15+
/// Gets the file provider, if available, that supplies the static files for the SPA.
16+
/// The value is <c>null</c> if no file provider is available.
17+
/// </summary>
18+
IFileProvider FileProvider { get; }
19+
}
20+
}
Collapse file
+125Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.SpaServices.StaticFiles;
6+
using Microsoft.Extensions.FileProviders;
7+
using Microsoft.Extensions.Options;
8+
using System;
9+
10+
namespace Microsoft.Extensions.DependencyInjection
11+
{
12+
/// <summary>
13+
/// Extension methods for configuring an application to serve static files for a
14+
/// Single Page Application (SPA).
15+
/// </summary>
16+
public static class SpaStaticFilesExtensions
17+
{
18+
/// <summary>
19+
/// Registers an <see cref="ISpaStaticFileProvider"/> service that can provide static
20+
/// files to be served for a Single Page Application (SPA).
21+
/// </summary>
22+
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
23+
/// <param name="configuration">If specified, this callback will be invoked to set additional configuration options.</param>
24+
public static void AddSpaStaticFiles(
25+
this IServiceCollection services,
26+
Action<SpaStaticFilesOptions> configuration = null)
27+
{
28+
services.AddSingleton<ISpaStaticFileProvider>(serviceProvider =>
29+
{
30+
// Use the options configured in DI (or blank if none was configured)
31+
var optionsProvider = serviceProvider.GetService<IOptions<SpaStaticFilesOptions>>();
32+
var options = optionsProvider.Value;
33+
34+
// Allow the developer to perform further configuration
35+
configuration?.Invoke(options);
36+
37+
if (string.IsNullOrEmpty(options.RootPath))
38+
{
39+
throw new InvalidOperationException($"No {nameof(SpaStaticFilesOptions.RootPath)} " +
40+
$"was set on the {nameof(SpaStaticFilesOptions)}.");
41+
}
42+
43+
return new DefaultSpaStaticFileProvider(serviceProvider, options);
44+
});
45+
}
46+
47+
/// <summary>
48+
/// Configures the application to serve static files for a Single Page Application (SPA).
49+
/// The files will be located using the registered <see cref="ISpaStaticFileProvider"/> service.
50+
/// </summary>
51+
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
52+
public static void UseSpaStaticFiles(this IApplicationBuilder applicationBuilder)
53+
{
54+
if (applicationBuilder == null)
55+
{
56+
throw new ArgumentNullException(nameof(applicationBuilder));
57+
}
58+
59+
UseSpaStaticFilesInternal(applicationBuilder,
60+
overrideFileProvider: null,
61+
allowFallbackOnServingWebRootFiles: false);
62+
}
63+
64+
internal static void UseSpaStaticFilesInternal(
65+
this IApplicationBuilder app,
66+
IFileProvider overrideFileProvider,
67+
bool allowFallbackOnServingWebRootFiles)
68+
{
69+
var shouldServeStaticFiles = ShouldServeStaticFiles(
70+
app,
71+
overrideFileProvider,
72+
allowFallbackOnServingWebRootFiles,
73+
out var fileProviderOrDefault);
74+
75+
if (shouldServeStaticFiles)
76+
{
77+
app.UseStaticFiles(new StaticFileOptions
78+
{
79+
FileProvider = fileProviderOrDefault
80+
});
81+
}
82+
}
83+
84+
private static bool ShouldServeStaticFiles(
85+
IApplicationBuilder app,
86+
IFileProvider overrideFileProvider,
87+
bool allowFallbackOnServingWebRootFiles,
88+
out IFileProvider fileProviderOrDefault)
89+
{
90+
if (overrideFileProvider != null)
91+
{
92+
// If the file provider was explicitly supplied, that takes precedence over any other
93+
// configured file provider. This is most useful if the application hosts multiple SPAs
94+
// (via multiple calls to UseSpa()), so each needs to serve its own separate static files
95+
// instead of using AddSpaStaticFiles/UseSpaStaticFiles.
96+
fileProviderOrDefault = overrideFileProvider;
97+
return true;
98+
}
99+
100+
var spaStaticFilesService = app.ApplicationServices.GetService<ISpaStaticFileProvider>();
101+
if (spaStaticFilesService != null)
102+
{
103+
// If an ISpaStaticFileProvider was configured but it says no IFileProvider is available
104+
// (i.e., it supplies 'null'), this implies we should not serve any static files. This
105+
// is typically the case in development when SPA static files are being served from a
106+
// SPA development server (e.g., Angular CLI or create-react-app), in which case no
107+
// directory of prebuilt files will exist on disk.
108+
fileProviderOrDefault = spaStaticFilesService.FileProvider;
109+
return fileProviderOrDefault != null;
110+
}
111+
else if (!allowFallbackOnServingWebRootFiles)
112+
{
113+
throw new InvalidOperationException($"To use {nameof(UseSpaStaticFiles)}, you must " +
114+
$"first register an {nameof(ISpaStaticFileProvider)} in the service provider, typically " +
115+
$"by calling services.{nameof(AddSpaStaticFiles)}.");
116+
}
117+
else
118+
{
119+
// Fall back on serving wwwroot
120+
fileProviderOrDefault = null;
121+
return true;
122+
}
123+
}
124+
}
125+
}
Collapse file
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 Microsoft.Extensions.DependencyInjection;
5+
6+
namespace Microsoft.AspNetCore.SpaServices.StaticFiles
7+
{
8+
/// <summary>
9+
/// Represents options for serving static files for a Single Page Application (SPA).
10+
/// </summary>
11+
public class SpaStaticFilesOptions
12+
{
13+
/// <summary>
14+
/// Gets or sets the path, relative to the application root, of the directory in which
15+
/// the physical files are located.
16+
///
17+
/// If the specified directory does not exist, then the
18+
/// <see cref="SpaStaticFilesExtensions.UseSpaStaticFiles(Builder.IApplicationBuilder)"/>
19+
/// middleware will not serve any static files.
20+
/// </summary>
21+
public string RootPath { get; set; }
22+
}
23+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.