mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-22 17:47:53 +00:00
AddSpaStaticFiles/UseSpaStaticFiles APIs to clean up the React template (or other cases where SPA files are outside wwwroot)
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
||||||
<MicrosoftAspNetCoreWebSocketsPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreWebSocketsPackageVersion>
|
<MicrosoftAspNetCoreWebSocketsPackageVersion>2.1.0-preview1-27478</MicrosoftAspNetCoreWebSocketsPackageVersion>
|
||||||
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
||||||
|
<MicrosoftExtensionsFileProvidersPhysicalPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsFileProvidersPhysicalPackageVersion>
|
||||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsLoggingConsolePackageVersion>
|
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||||
<MicrosoftExtensionsLoggingDebugPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsLoggingDebugPackageVersion>
|
<MicrosoftExtensionsLoggingDebugPackageVersion>2.1.0-preview1-27478</MicrosoftExtensionsLoggingDebugPackageVersion>
|
||||||
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="$(MicrosoftAspNetCoreWebSocketsPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="$(MicrosoftAspNetCoreWebSocketsPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="$(MicrosoftExtensionsFileProvidersPhysicalPackageVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SpaServices
|
namespace Microsoft.AspNetCore.SpaServices
|
||||||
@@ -26,11 +27,12 @@ namespace Microsoft.AspNetCore.SpaServices
|
|||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve it as file from wwwroot (by default), or any other configured file provider
|
// Serve it as a static file
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
// Developers who need to host more than one SPA with distinct default pages can
|
||||||
{
|
// override the file provider
|
||||||
FileProvider = options.DefaultPageFileProvider
|
app.UseSpaStaticFilesInternal(
|
||||||
});
|
overrideFileProvider: options.DefaultPageFileProvider,
|
||||||
|
allowFallbackOnServingWebRootFiles: true);
|
||||||
|
|
||||||
// If the default file didn't get served as a static file (usually because it was not
|
// If the default file didn't get served as a static file (usually because it was not
|
||||||
// present on disk), the SPA is definitely not going to work.
|
// present on disk), the SPA is definitely not going to work.
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// 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 Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an implementation of <see cref="ISpaStaticFileProvider"/> that supplies
|
||||||
|
/// physical files at a location configured using <see cref="SpaStaticFilesOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal class DefaultSpaStaticFileProvider : ISpaStaticFileProvider
|
||||||
|
{
|
||||||
|
private IFileProvider _fileProvider;
|
||||||
|
|
||||||
|
public DefaultSpaStaticFileProvider(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
SpaStaticFilesOptions options)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(options.RootPath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"The {nameof(options.RootPath)} property " +
|
||||||
|
$"of {nameof(options)} cannot be null or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var env = serviceProvider.GetRequiredService<IHostingEnvironment>();
|
||||||
|
var absoluteRootPath = Path.Combine(
|
||||||
|
env.ContentRootPath,
|
||||||
|
options.RootPath);
|
||||||
|
|
||||||
|
// PhysicalFileProvider will throw if you pass a non-existent path,
|
||||||
|
// but we don't want that scenario to be an error because for SPA
|
||||||
|
// scenarios, it's better if non-existing directory just means we
|
||||||
|
// don't serve any static files.
|
||||||
|
if (Directory.Exists(absoluteRootPath))
|
||||||
|
{
|
||||||
|
_fileProvider = new PhysicalFileProvider(absoluteRootPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFileProvider FileProvider => _fileProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// 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 Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a service that can provide static files to be served for a Single Page
|
||||||
|
/// Application (SPA).
|
||||||
|
/// </summary>
|
||||||
|
public interface ISpaStaticFileProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file provider, if available, that supplies the static files for the SPA.
|
||||||
|
/// The value is <c>null</c> if no file provider is available.
|
||||||
|
/// </summary>
|
||||||
|
IFileProvider FileProvider { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
// 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 Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.StaticFiles;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (applicationBuilder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(applicationBuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
UseSpaStaticFilesInternal(applicationBuilder,
|
||||||
|
overrideFileProvider: null,
|
||||||
|
allowFallbackOnServingWebRootFiles: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void UseSpaStaticFilesInternal(
|
||||||
|
this IApplicationBuilder app,
|
||||||
|
IFileProvider overrideFileProvider,
|
||||||
|
bool allowFallbackOnServingWebRootFiles)
|
||||||
|
{
|
||||||
|
var shouldServeStaticFiles = ShouldServeStaticFiles(
|
||||||
|
app,
|
||||||
|
overrideFileProvider,
|
||||||
|
allowFallbackOnServingWebRootFiles,
|
||||||
|
out var fileProviderOrDefault);
|
||||||
|
|
||||||
|
if (shouldServeStaticFiles)
|
||||||
|
{
|
||||||
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
|
{
|
||||||
|
FileProvider = fileProviderOrDefault
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldServeStaticFiles(
|
||||||
|
IApplicationBuilder app,
|
||||||
|
IFileProvider overrideFileProvider,
|
||||||
|
bool allowFallbackOnServingWebRootFiles,
|
||||||
|
out IFileProvider fileProviderOrDefault)
|
||||||
|
{
|
||||||
|
if (overrideFileProvider != null)
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
fileProviderOrDefault = overrideFileProvider;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// 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 Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SpaServices.StaticFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents options for serving static files for a Single Page Application (SPA).
|
||||||
|
/// </summary>
|
||||||
|
public class SpaStaticFilesOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path, relative to the application root, of the directory in which
|
||||||
|
/// the physical files are located.
|
||||||
|
///
|
||||||
|
/// If the specified directory does not exist, then the
|
||||||
|
/// <see cref="SpaStaticFilesExtensions.UseSpaStaticFiles(Builder.IApplicationBuilder)"/>
|
||||||
|
/// middleware will not serve any static files.
|
||||||
|
/// </summary>
|
||||||
|
public string RootPath { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user