177 lines
7.3 KiB
C#
177 lines
7.3 KiB
C#
// 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.Http;
|
|
using Microsoft.AspNetCore.Blazor.Server;
|
|
using Microsoft.AspNetCore.StaticFiles;
|
|
using Microsoft.Extensions.FileProviders;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.Net.Http.Headers;
|
|
using System.Net.Mime;
|
|
using System;
|
|
using System.IO;
|
|
|
|
namespace Microsoft.AspNetCore.Builder
|
|
{
|
|
/// <summary>
|
|
/// Provides extension methods that add Blazor-related middleware to the ASP.NET pipeline.
|
|
/// </summary>
|
|
public static class BlazorAppBuilderExtensions
|
|
{
|
|
const string DevServerApplicationName = "dotnet-blazor";
|
|
|
|
/// <summary>
|
|
/// Configures the middleware pipeline to work with Blazor.
|
|
/// </summary>
|
|
/// <typeparam name="TProgram">Any type from the client app project. This is used to identify the client app assembly.</typeparam>
|
|
/// <param name="app"></param>
|
|
public static void UseBlazor<TProgram>(
|
|
this IApplicationBuilder app)
|
|
{
|
|
var clientAssemblyInServerBinDir = typeof(TProgram).Assembly;
|
|
app.UseBlazor(new BlazorOptions
|
|
{
|
|
ClientAssemblyPath = clientAssemblyInServerBinDir.Location,
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configures the middleware pipeline to work with Blazor.
|
|
/// </summary>
|
|
/// <param name="app"></param>
|
|
/// <param name="options"></param>
|
|
public static void UseBlazor(
|
|
this IApplicationBuilder app,
|
|
BlazorOptions options)
|
|
{
|
|
// TODO: Make the .blazor.config file contents sane
|
|
// Currently the items in it are bizarre and don't relate to their purpose,
|
|
// hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either.
|
|
var env = (IHostingEnvironment)app.ApplicationServices.GetService(typeof(IHostingEnvironment));
|
|
var config = BlazorConfig.Read(options.ClientAssemblyPath);
|
|
|
|
if (env.IsDevelopment() && config.EnableAutoRebuilding)
|
|
{
|
|
if (env.ApplicationName.Equals(DevServerApplicationName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
app.UseDevServerAutoRebuild(config);
|
|
}
|
|
else
|
|
{
|
|
app.UseHostedAutoRebuild(config, env.ContentRootPath);
|
|
}
|
|
}
|
|
|
|
// First, match the request against files in the client app dist directory
|
|
app.UseStaticFiles(new StaticFileOptions
|
|
{
|
|
FileProvider = new PhysicalFileProvider(config.DistPath),
|
|
ContentTypeProvider = CreateContentTypeProvider(config.EnableDebugging),
|
|
OnPrepareResponse = SetCacheHeaders
|
|
});
|
|
|
|
// * Before publishing, we serve the wwwroot files directly from source
|
|
// (and don't require them to be copied into dist).
|
|
// In this case, WebRootPath will be nonempty if that directory exists.
|
|
// * After publishing, the wwwroot files are already copied to 'dist' and
|
|
// will be served by the above middleware, so we do nothing here.
|
|
// In this case, WebRootPath will be empty (the publish process sets this).
|
|
if (!string.IsNullOrEmpty(config.WebRootPath))
|
|
{
|
|
app.UseStaticFiles(new StaticFileOptions
|
|
{
|
|
FileProvider = new PhysicalFileProvider(config.WebRootPath),
|
|
OnPrepareResponse = SetCacheHeaders
|
|
});
|
|
}
|
|
|
|
// Accept debugger connections
|
|
if (config.EnableDebugging)
|
|
{
|
|
app.UseMonoDebugProxy();
|
|
}
|
|
|
|
// Finally, use SPA fallback routing (serve default page for anything else,
|
|
// excluding /_framework/*)
|
|
app.MapWhen(IsNotFrameworkDir, childAppBuilder =>
|
|
{
|
|
var indexHtmlPath = FindIndexHtmlFile(config);
|
|
var indexHtmlStaticFileOptions = string.IsNullOrEmpty(indexHtmlPath)
|
|
? null : new StaticFileOptions
|
|
{
|
|
FileProvider = new PhysicalFileProvider(Path.GetDirectoryName(indexHtmlPath)),
|
|
OnPrepareResponse = SetCacheHeaders
|
|
};
|
|
|
|
childAppBuilder.UseSpa(spa =>
|
|
{
|
|
spa.Options.DefaultPageStaticFileOptions = indexHtmlStaticFileOptions;
|
|
});
|
|
});
|
|
}
|
|
|
|
private static string FindIndexHtmlFile(BlazorConfig config)
|
|
{
|
|
// Before publishing, the client project may have a wwwroot directory.
|
|
// If so, and if it contains index.html, use that.
|
|
if (!string.IsNullOrEmpty(config.WebRootPath))
|
|
{
|
|
var wwwrootIndexHtmlPath = Path.Combine(config.WebRootPath, "index.html");
|
|
if (File.Exists(wwwrootIndexHtmlPath))
|
|
{
|
|
return wwwrootIndexHtmlPath;
|
|
}
|
|
}
|
|
|
|
// After publishing, the client project won't have a wwwroot directory.
|
|
// The contents from that dir will have been copied to "dist" during publish.
|
|
// So if "dist/index.html" now exists, use that.
|
|
var distIndexHtmlPath = Path.Combine(config.DistPath, "index.html");
|
|
if (File.Exists(distIndexHtmlPath))
|
|
{
|
|
return distIndexHtmlPath;
|
|
}
|
|
|
|
// Since there's no index.html, we'll use the default DefaultPageStaticFileOptions,
|
|
// hence we'll look for index.html in the host server app's wwwroot.
|
|
return null;
|
|
}
|
|
|
|
private static void SetCacheHeaders(StaticFileResponseContext ctx)
|
|
{
|
|
// By setting "Cache-Control: no-cache", we're allowing the browser to store
|
|
// a cached copy of the response, but telling it that it must check with the
|
|
// server for modifications (based on Etag) before using that cached copy.
|
|
// Longer term, we should generate URLs based on content hashes (at least
|
|
// for published apps) so that the browser doesn't need to make any requests
|
|
// for unchanged files.
|
|
var headers = ctx.Context.Response.GetTypedHeaders();
|
|
if (headers.CacheControl == null)
|
|
{
|
|
headers.CacheControl = new CacheControlHeaderValue
|
|
{
|
|
NoCache = true
|
|
};
|
|
}
|
|
}
|
|
|
|
private static bool IsNotFrameworkDir(HttpContext context)
|
|
=> !context.Request.Path.StartsWithSegments("/_framework");
|
|
|
|
private static IContentTypeProvider CreateContentTypeProvider(bool enableDebugging)
|
|
{
|
|
var result = new FileExtensionContentTypeProvider();
|
|
result.Mappings.Add(".dll", MediaTypeNames.Application.Octet);
|
|
result.Mappings.Add(".mem", MediaTypeNames.Application.Octet);
|
|
result.Mappings.Add(".wasm", WasmMediaTypeNames.Application.Wasm);
|
|
|
|
if (enableDebugging)
|
|
{
|
|
result.Mappings.Add(".pdb", MediaTypeNames.Application.Octet);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|