Add support for configuration (#19544)
* Add support for configuration Fixes https://github.com/dotnet/aspnetcore/issues/18675
This commit is contained in:
parent
0006fdf093
commit
147c39289a
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -4,10 +4,12 @@ import * as Environment from './Environment';
|
|||
import { monoPlatform } from './Platform/Mono/MonoPlatform';
|
||||
import { renderBatch } from './Rendering/Renderer';
|
||||
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
|
||||
import { Pointer } from './Platform/Platform';
|
||||
import { shouldAutoStart } from './BootCommon';
|
||||
import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
|
||||
import { WebAssemblyResourceLoader } from './Platform/WebAssemblyResourceLoader';
|
||||
import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader';
|
||||
import { BootConfigResult } from './Platform/BootConfig';
|
||||
import { Pointer } from './Platform/Platform';
|
||||
|
||||
let started = false;
|
||||
|
||||
|
|
@ -38,7 +40,12 @@ async function boot(options?: any): Promise<void> {
|
|||
});
|
||||
|
||||
// Fetch the resources and prepare the Mono runtime
|
||||
const resourceLoader = await WebAssemblyResourceLoader.initAsync();
|
||||
const bootConfigResult = await BootConfigResult.initAsync();
|
||||
|
||||
const [resourceLoader] = await Promise.all([
|
||||
WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig),
|
||||
WebAssemblyConfigLoader.initAsync(bootConfigResult)]);
|
||||
|
||||
try {
|
||||
await platform.start(resourceLoader);
|
||||
} catch (ex) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
export class BootConfigResult {
|
||||
private constructor(public bootConfig: BootJsonData, public applicationEnvironment: string) {
|
||||
}
|
||||
|
||||
static async initAsync(): Promise<BootConfigResult> {
|
||||
const bootConfigResponse = await fetch('_framework/blazor.boot.json', {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
cache: 'no-cache'
|
||||
});
|
||||
|
||||
// While we can expect an ASP.NET Core hosted application to include the environment, other
|
||||
// hosts may not. Assume 'Production' in the absenc of any specified value.
|
||||
const applicationEnvironment = bootConfigResponse.headers.get('Blazor-Environment') || 'Production';
|
||||
const bootConfig: BootJsonData = await bootConfigResponse.json();
|
||||
|
||||
return new BootConfigResult(bootConfig, applicationEnvironment);
|
||||
};
|
||||
}
|
||||
|
||||
// Keep in sync with bootJsonData in Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||
export interface BootJsonData {
|
||||
readonly entryAssembly: string;
|
||||
readonly resources: ResourceGroups;
|
||||
readonly debugBuild: boolean;
|
||||
readonly linkerEnabled: boolean;
|
||||
readonly cacheBootResources: boolean;
|
||||
readonly config: string[];
|
||||
}
|
||||
|
||||
export interface ResourceGroups {
|
||||
readonly assembly: ResourceList;
|
||||
readonly pdb?: ResourceList;
|
||||
readonly runtime: ResourceList;
|
||||
}
|
||||
|
||||
export type ResourceList = { [name: string]: string };
|
||||
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
|
||||
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
|
||||
import { showErrorNotification } from '../../BootErrors';
|
||||
import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResourceLoader';
|
||||
import { Platform, System_Array, Pointer, System_Object, System_String } from '../Platform';
|
||||
|
||||
let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr;
|
||||
let mono_string_get_utf8: (managedString: System_String) => Pointer;
|
||||
let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void;
|
||||
const appBinDirName = 'appBinDir';
|
||||
const uint64HighOrderShift = Math.pow(2, 32);
|
||||
|
|
@ -47,7 +47,7 @@ export const monoPlatform: Platform = {
|
|||
// FIXME this is unsafe, cuz raw objects could be GC'd.
|
||||
|
||||
const utf8 = mono_string_get_utf8(managedString);
|
||||
const res = Module.UTF8ToString(utf8);
|
||||
const res = (<any>window['Module']).UTF8ToString(utf8);
|
||||
Module._free(utf8 as any);
|
||||
return res;
|
||||
},
|
||||
|
|
@ -168,7 +168,7 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
|
|||
};
|
||||
module.preRun = [];
|
||||
module.postRun = [];
|
||||
module.preloadPlugins = [];
|
||||
(module as any).preloadPlugins = [];
|
||||
|
||||
// Override the mechanism for fetching the main wasm file so we can connect it to our cache
|
||||
module.instantiateWasm = (imports, successCallback): WebAssembly.Exports => {
|
||||
|
|
@ -258,10 +258,10 @@ function getArrayDataPointer<T>(array: System_Array<T>): number {
|
|||
return <number><any>array + 12; // First byte from here is length, then following bytes are entries
|
||||
}
|
||||
|
||||
function bindStaticMethod(assembly: string, typeName: string, method: string) : (...args: any[]) => any {
|
||||
function bindStaticMethod(assembly: string, typeName: string, method: string) {
|
||||
// Fully qualified name looks like this: "[debugger-test] Math:IntAdd"
|
||||
const fqn = `[${assembly}] ${typeName}:${method}`;
|
||||
return Module.mono_bind_static_method(fqn);
|
||||
return BINDING.bind_static_method(fqn);
|
||||
}
|
||||
|
||||
function attachInteropInvoker(): void {
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
declare namespace Module {
|
||||
function UTF8ToString(utf8: Mono.Utf8Ptr): string;
|
||||
var preloadPlugins: any[];
|
||||
|
||||
function stackSave(): Mono.StackSaveHandle;
|
||||
function stackAlloc(length: number): number;
|
||||
function stackRestore(handle: Mono.StackSaveHandle): void;
|
||||
|
||||
// These should probably be in @types/emscripten
|
||||
function FS_createPath(parent, path, canRead, canWrite);
|
||||
function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn);
|
||||
|
||||
function mono_bind_static_method(fqn: string): BoundStaticMethod;
|
||||
}
|
||||
|
||||
declare namespace Mono {
|
||||
interface Utf8Ptr { Utf8Ptr__DO_NOT_IMPLEMENT: any }
|
||||
interface StackSaveHandle { StackSaveHandle__DO_NOT_IMPLEMENT: any }
|
||||
}
|
||||
|
||||
// Mono uses this global to hang various debugging-related items on
|
||||
declare namespace MONO {
|
||||
var loaded_files: string[];
|
||||
var mono_wasm_runtime_is_ready: boolean;
|
||||
function mono_wasm_runtime_ready (): void;
|
||||
function mono_wasm_setenv (name: string, value: string): void;
|
||||
}
|
||||
|
||||
// mono_bind_static_method allows arbitrary JS data types to be sent over the wire. However we are
|
||||
// artifically limiting it to a subset of types that we actually use.
|
||||
declare type BoundStaticMethod = (...args: (string | number | null)[]) => (string | number | null);
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { Pointer, System_String } from '../Platform';
|
||||
|
||||
// Mono uses this global to hang various debugging-related items on
|
||||
|
||||
declare interface MONO {
|
||||
loaded_files: string[];
|
||||
mono_wasm_runtime_ready (): void;
|
||||
mono_wasm_setenv (name: string, value: string): void;
|
||||
}
|
||||
|
||||
// Mono uses this global to hold low-level interop APIs
|
||||
declare interface BINDING {
|
||||
js_string_to_mono_string(jsString: string): System_String;
|
||||
js_typed_array_to_array(array: Uint8Array): Pointer;
|
||||
js_typed_array_to_array<T>(array: Array<T>): Pointer;
|
||||
conv_string(dotnetString: System_String | null): string | null;
|
||||
bind_static_method(fqn: string, signature?: string): Function;
|
||||
}
|
||||
|
||||
declare global {
|
||||
var MONO: MONO;
|
||||
var BINDING: BINDING;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader";
|
||||
import { WebAssemblyResourceLoader } from './WebAssemblyResourceLoader';
|
||||
|
||||
export interface Platform {
|
||||
start(resourceLoader: WebAssemblyResourceLoader): Promise<void>;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import { BootConfigResult } from './BootConfig';
|
||||
import { System_String, Pointer } from './Platform';
|
||||
|
||||
export class WebAssemblyConfigLoader {
|
||||
static async initAsync(bootConfigResult: BootConfigResult): Promise<void> {
|
||||
window['Blazor']._internal.getApplicationEnvironment = () => BINDING.js_string_to_mono_string(bootConfigResult.applicationEnvironment);
|
||||
|
||||
const configFiles = await Promise.all((bootConfigResult.bootConfig.config || [])
|
||||
.filter(name => name === 'appsettings.json' || name === `appsettings.${bootConfigResult.applicationEnvironment}.json`)
|
||||
.map(async name => ({ name, content: await getConfigBytes(name) })));
|
||||
|
||||
window['Blazor']._internal.getConfig = (dotNetFileName: System_String) : Pointer | undefined => {
|
||||
const fileName = BINDING.conv_string(dotNetFileName);
|
||||
const resolvedFile = configFiles.find(f => f.name === fileName);
|
||||
return resolvedFile ? BINDING.js_typed_array_to_array(resolvedFile.content) : undefined;
|
||||
};
|
||||
|
||||
async function getConfigBytes(file: string): Promise<Uint8Array> {
|
||||
const response = await fetch(file, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
cache: 'no-cache'
|
||||
});
|
||||
|
||||
return new Uint8Array(await response.arrayBuffer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { toAbsoluteUri } from '../Services/NavigationManager';
|
||||
import { BootJsonData, ResourceList } from './BootConfig';
|
||||
const networkFetchCacheMode = 'no-cache';
|
||||
|
||||
export class WebAssemblyResourceLoader {
|
||||
|
|
@ -6,20 +7,12 @@ export class WebAssemblyResourceLoader {
|
|||
private networkLoads: { [name: string]: LoadLogEntry } = {};
|
||||
private cacheLoads: { [name: string]: LoadLogEntry } = {};
|
||||
|
||||
static async initAsync(): Promise<WebAssemblyResourceLoader> {
|
||||
const bootConfigResponse = await fetch('_framework/blazor.boot.json', {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
cache: networkFetchCacheMode
|
||||
});
|
||||
const bootConfig: BootJsonData = await bootConfigResponse.json();
|
||||
static async initAsync(bootConfig: BootJsonData): Promise<WebAssemblyResourceLoader> {
|
||||
const cache = await getCacheToUseIfEnabled(bootConfig);
|
||||
|
||||
return new WebAssemblyResourceLoader(bootConfig, cache);
|
||||
}
|
||||
|
||||
constructor (public readonly bootConfig: BootJsonData, private cacheIfUsed: Cache | null)
|
||||
{
|
||||
constructor(readonly bootConfig: BootJsonData, readonly cacheIfUsed: Cache | null) {
|
||||
}
|
||||
|
||||
loadResources(resources: ResourceList, url: (name: string) => string): LoadingResource[] {
|
||||
|
|
@ -36,7 +29,7 @@ export class WebAssemblyResourceLoader {
|
|||
? this.loadResourceWithCaching(this.cacheIfUsed, name, url, contentHash)
|
||||
: fetch(url, { cache: networkFetchCacheMode, integrity: this.bootConfig.cacheBootResources ? contentHash : undefined });
|
||||
|
||||
return { name, url, response };
|
||||
return { name, url, response };
|
||||
}
|
||||
|
||||
logToConsole() {
|
||||
|
|
@ -166,7 +159,7 @@ function countTotalBytes(loads: LoadLogEntry[]) {
|
|||
}
|
||||
|
||||
function toDataSizeString(byteCount: number) {
|
||||
return `${(byteCount / (1024*1024)).toFixed(2)} MB`;
|
||||
return `${(byteCount / (1024 * 1024)).toFixed(2)} MB`;
|
||||
}
|
||||
|
||||
function getPerformanceEntry(url: string): PerformanceResourceTiming | undefined {
|
||||
|
|
@ -175,21 +168,6 @@ function getPerformanceEntry(url: string): PerformanceResourceTiming | undefined
|
|||
}
|
||||
}
|
||||
|
||||
// Keep in sync with bootJsonData in Microsoft.AspNetCore.Blazor.Build
|
||||
interface BootJsonData {
|
||||
readonly entryAssembly: string;
|
||||
readonly resources: ResourceGroups;
|
||||
readonly debugBuild: boolean;
|
||||
readonly linkerEnabled: boolean;
|
||||
readonly cacheBootResources: boolean;
|
||||
}
|
||||
|
||||
interface ResourceGroups {
|
||||
readonly assembly: ResourceList;
|
||||
readonly pdb?: ResourceList;
|
||||
readonly runtime: ResourceList;
|
||||
}
|
||||
|
||||
interface LoadLogEntry {
|
||||
responseBytes: number | undefined;
|
||||
}
|
||||
|
|
@ -199,5 +177,3 @@ export interface LoadingResource {
|
|||
url: string;
|
||||
response: Promise<Response>;
|
||||
}
|
||||
|
||||
type ResourceList = { [name: string]: string };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { platform } from '../../Environment';
|
||||
import { RenderBatch, ArrayRange, ArrayRangeReader, ArrayBuilderSegment, RenderTreeDiff, RenderTreeEdit, RenderTreeFrame, ArrayValues, EditType, FrameType, RenderTreeFrameReader } from './RenderBatch';
|
||||
import { RenderBatch, ArrayRange, ArrayBuilderSegment, RenderTreeDiff, RenderTreeEdit, RenderTreeFrame, ArrayValues, EditType, FrameType, RenderTreeFrameReader } from './RenderBatch';
|
||||
import { Pointer, System_Array, System_Object } from '../../Platform/Platform';
|
||||
|
||||
// Used when running on Mono WebAssembly for shared-memory interop. The code here encapsulates
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
[Required]
|
||||
public bool CacheBootResources { get; set; }
|
||||
|
||||
public ITaskItem[] ConfigurationFiles { get; set; }
|
||||
|
||||
[Required]
|
||||
public string OutputPath { get; set; }
|
||||
|
||||
|
|
@ -59,7 +61,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
cacheBootResources = CacheBootResources,
|
||||
debugBuild = DebugBuild,
|
||||
linkerEnabled = LinkerEnabled,
|
||||
resources = new Dictionary<ResourceType, ResourceHashesByNameDictionary>()
|
||||
resources = new Dictionary<ResourceType, ResourceHashesByNameDictionary>(),
|
||||
config = new List<string>(),
|
||||
};
|
||||
|
||||
// Build a two-level dictionary of the form:
|
||||
|
|
@ -90,6 +93,14 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
}
|
||||
}
|
||||
|
||||
if (ConfigurationFiles != null)
|
||||
{
|
||||
foreach (var configFile in ConfigurationFiles)
|
||||
{
|
||||
result.config.Add(Path.GetFileName(configFile.ItemSpec));
|
||||
}
|
||||
}
|
||||
|
||||
var serializer = new DataContractJsonSerializer(typeof(BootJsonData), new DataContractJsonSerializerSettings
|
||||
{
|
||||
UseSimpleDictionaryFormat = true
|
||||
|
|
@ -148,6 +159,11 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
/// Gets a value that determines if the linker is enabled.
|
||||
/// </summary>
|
||||
public bool linkerEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Config files for the application
|
||||
/// </summary>
|
||||
public List<string> config { get; set; }
|
||||
}
|
||||
|
||||
public enum ResourceType
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@
|
|||
$(ComponentsWebAssemblyFrameworkPath)" />
|
||||
|
||||
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
|
||||
|
||||
<_BlazorConfigFile Include="wwwroot\appsettings*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
|
|
@ -298,12 +300,43 @@
|
|||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_GenerateBlazorBootJsonInputHash">
|
||||
<ItemGroup>
|
||||
<_BlazorBootJsonHashInput Include="@(IntermediateAssembly)" />
|
||||
<_BlazorBootJsonHashInput Include="@(_BlazorOutputWithTargetPath)" />
|
||||
<_BlazorBootJsonHashInput Include="@(_BlazorConfigFile)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Hash ItemsToHash="@(_BlazorBootJsonHashInput)">
|
||||
<Output TaskParameter="HashResult" PropertyName="_BlazorBootJsonInputHash" />
|
||||
</Hash>
|
||||
|
||||
<PropertyGroup>
|
||||
<_BlazorBootJsonInputHashFile>$(_BlazorIntermediateOutputPath)boot.json.input</_BlazorBootJsonInputHashFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
Lines="$(_BlazorBootJsonInputHash)"
|
||||
File="$(_BlazorBootJsonInputHashFile)"
|
||||
Overwrite="True"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="GenerateBlazorBootJson" AssemblyFile="$(_BlazorTasksPath)" />
|
||||
|
||||
<Target
|
||||
Name="_GenerateBlazorBootJson"
|
||||
Inputs="$(MSBuildAllProjects);@(_BlazorOutputWithTargetPath)"
|
||||
DependsOnTargets="_GenerateBlazorBootJsonInputHash"
|
||||
Inputs="$(MSBuildAllProjects);@(_BlazorOutputWithTargetPath);$(_BlazorBootJsonInputHashFile)"
|
||||
Outputs="$(_BlazorBootJsonIntermediateOutputPath)">
|
||||
|
||||
<PropertyGroup>
|
||||
<_IsDebugBuild>false</_IsDebugBuild>
|
||||
<_IsDebugBuild Condition="'$(Configuration)' == 'Debug'">true</_IsDebugBuild>
|
||||
<BlazorCacheBootResources Condition="'$(BlazorCacheBootResources)' == ''">true</BlazorCacheBootResources>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorBootResource Include="@(_BlazorOutputWithTargetPath->HasMetadata('BootManifestResourceType'))" />
|
||||
</ItemGroup>
|
||||
|
|
@ -312,18 +345,14 @@
|
|||
<Output TaskParameter="Items" ItemName="_BlazorBootResourceWithHash" />
|
||||
</GetFileHash>
|
||||
|
||||
<PropertyGroup>
|
||||
<_IsDebugBuild>false</_IsDebugBuild>
|
||||
<_IsDebugBuild Condition="'$(Configuration)' == 'Debug'">true</_IsDebugBuild>
|
||||
<BlazorCacheBootResources Condition="'$(BlazorCacheBootResources)' == ''">true</BlazorCacheBootResources>
|
||||
</PropertyGroup>
|
||||
<GenerateBlazorBootJson
|
||||
AssemblyPath="@(IntermediateAssembly)"
|
||||
Resources="@(_BlazorBootResourceWithHash)"
|
||||
DebugBuild="$(_IsDebugBuild)"
|
||||
LinkerEnabled="$(BlazorWebAssemblyEnableLinking)"
|
||||
CacheBootResources="$(BlazorCacheBootResources)"
|
||||
OutputPath="$(_BlazorBootJsonIntermediateOutputPath)" />
|
||||
OutputPath="$(_BlazorBootJsonIntermediateOutputPath)"
|
||||
ConfigurationFiles="@(_BlazorConfigFile)" />
|
||||
|
||||
<ItemGroup>
|
||||
<_BlazorOutputWithTargetPath Include="$(_BlazorBootJsonIntermediateOutputPath)" TargetOutputPath="$(_BaseBlazorRuntimeOutputPath)$(_BlazorBootJsonName)" />
|
||||
|
|
|
|||
|
|
@ -51,8 +51,13 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
"Microsoft.Bcl.AsyncInterfaces.dll",
|
||||
"Microsoft.Extensions.Configuration.Abstractions.dll",
|
||||
"Microsoft.Extensions.Configuration.dll",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions.dll",
|
||||
"Microsoft.Extensions.Configuration.Json.dll",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"Microsoft.Extensions.DependencyInjection.dll",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions.dll",
|
||||
"Microsoft.Extensions.FileProviders.Physical.dll",
|
||||
"Microsoft.Extensions.FileSystemGlobbing.dll",
|
||||
"Microsoft.Extensions.Logging.Abstractions.dll",
|
||||
"Microsoft.Extensions.Primitives.dll",
|
||||
"Microsoft.JSInterop.dll",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server
|
||||
{
|
||||
|
|
@ -33,6 +32,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server
|
|||
|
||||
var inMemoryConfiguration = new Dictionary<string, string>
|
||||
{
|
||||
[WebHostDefaults.EnvironmentKey] = "Development",
|
||||
["Logging:LogLevel:Microsoft"] = "Warning",
|
||||
["Logging:LogLevel:Microsoft.Hosting.Lifetime"] = "Information",
|
||||
[WebHostDefaults.StaticWebAssetsKey] = name,
|
||||
|
|
|
|||
|
|
@ -51,10 +51,12 @@ namespace Microsoft.AspNetCore.Builder
|
|||
!rest.StartsWithSegments("/_framework/blazor.server.js"),
|
||||
subBuilder =>
|
||||
{
|
||||
subBuilder.Use(async (ctx, next) =>
|
||||
subBuilder.Use(async (context, next) =>
|
||||
{
|
||||
context.Response.Headers.Append("Blazor-Environment", webHostEnvironment.EnvironmentName);
|
||||
|
||||
// This will invoke the static files middleware plugged-in below.
|
||||
NegotiateEncoding(ctx, webHostEnvironment);
|
||||
NegotiateEncoding(context, webHostEnvironment);
|
||||
await next();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about the hosting environment an application is running in.
|
||||
/// </summary>
|
||||
public interface IWebAssemblyHostEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the environment. This is configured to use the environment of the application hosting the Blazor WebAssembly application.
|
||||
/// Configured to "Production" when not specified by the host.
|
||||
/// </summary>
|
||||
string Environment { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,13 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -59,6 +62,41 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
|||
{
|
||||
return Services.BuildServiceProvider();
|
||||
};
|
||||
|
||||
InitializeEnvironment();
|
||||
}
|
||||
|
||||
private void InitializeEnvironment()
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY")))
|
||||
{
|
||||
// The remainder of this method relies on the ability to make .NET WebAssembly-specific JSInterop calls.
|
||||
// Note that this short-circuit exists as a way for unit tests running in .NET Core without JSInterop to run.
|
||||
return;
|
||||
}
|
||||
|
||||
var applicationEnvironment = DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled<string>("Blazor._internal.getApplicationEnvironment");
|
||||
Services.AddSingleton<IWebAssemblyHostEnvironment>(new WebAssemblyHostEnvironment(applicationEnvironment));
|
||||
|
||||
var configFiles = new[]
|
||||
{
|
||||
"appsettings.json",
|
||||
$"appsettings.{applicationEnvironment}.json"
|
||||
};
|
||||
|
||||
foreach (var configFile in configFiles)
|
||||
{
|
||||
var appSettingsJson = DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled<string, byte[]>(
|
||||
"Blazor._internal.getConfig",
|
||||
configFile);
|
||||
|
||||
if (appSettingsJson != null)
|
||||
{
|
||||
// Perf: Using this over AddJsonStream. This allows the linker to trim out the "File"-specific APIs and assemblies
|
||||
// for Configuration, of where there are several.
|
||||
Configuration.Add<JsonStreamConfigurationSource>(s => s.Stream = new MemoryStream(appSettingsJson));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -130,7 +168,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
|||
return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray());
|
||||
}
|
||||
|
||||
private void InitializeDefaultServices()
|
||||
internal void InitializeDefaultServices()
|
||||
{
|
||||
Services.AddSingleton<IJSRuntime>(DefaultWebAssemblyJSRuntime.Instance);
|
||||
Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||
{
|
||||
internal sealed class WebAssemblyHostEnvironment : IWebAssemblyHostEnvironment
|
||||
{
|
||||
public WebAssemblyHostEnvironment(string environment) => Environment = environment;
|
||||
|
||||
public string Environment { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Components.Web" />
|
||||
<Reference Include="Microsoft.Extensions.Configuration" />
|
||||
<Reference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<Reference Include="Microsoft.JSInterop.WebAssembly" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||
{
|
||||
public class WebAssemblyConfigurationHostedTest : ServerTestBase<BasicTestAppServerSiteFixture<TestServer.ClientStartup>>
|
||||
{
|
||||
private IWebElement _appElement;
|
||||
|
||||
public WebAssemblyConfigurationHostedTest(
|
||||
BrowserFixture browserFixture,
|
||||
BasicTestAppServerSiteFixture<TestServer.ClientStartup> serverFixture,
|
||||
ITestOutputHelper output) :
|
||||
base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
base.InitializeAsyncCore();
|
||||
|
||||
Navigate(ServerPathBase, noReload: false);
|
||||
_appElement = Browser.MountTestComponent<ConfigurationComponent>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WebAssemblyConfiguration_Works()
|
||||
{
|
||||
// Verify values from the default 'appsettings.json' are read.
|
||||
Browser.Equal("Default key1-value", () => _appElement.FindElement(By.Id("key1")).Text);
|
||||
|
||||
// Verify values overriden by an environment specific 'appsettings.$(Environment).json are read
|
||||
Assert.Equal("Prod key2-value", _appElement.FindElement(By.Id("key2")).Text);
|
||||
|
||||
// Lastly for sanity, make sure values specified in an environment specific 'appsettings.$(Environment).json are read
|
||||
Assert.Equal("Prod key3-value", _appElement.FindElement(By.Id("key3")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WebAssemblyHostingEnvironment_Works()
|
||||
{
|
||||
// Verify values from the default 'appsettings.json' are read.
|
||||
Browser.Equal("Production", () => _appElement.FindElement(By.Id("environment")).Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using TestServer;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||
{
|
||||
public class WebAssemblyConfigurationTest : ServerTestBase<DevHostServerFixture<BasicTestApp.Program>>
|
||||
{
|
||||
private IWebElement _appElement;
|
||||
|
||||
public WebAssemblyConfigurationTest(
|
||||
BrowserFixture browserFixture,
|
||||
DevHostServerFixture<BasicTestApp.Program> serverFixture,
|
||||
ITestOutputHelper output) :
|
||||
base(browserFixture, serverFixture, output)
|
||||
{
|
||||
_serverFixture.PathBase = "/subdir";
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
base.InitializeAsyncCore();
|
||||
|
||||
Navigate(ServerPathBase, noReload: false);
|
||||
_appElement = Browser.MountTestComponent<ConfigurationComponent>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WebAssemblyConfiguration_Works()
|
||||
{
|
||||
// Verify values from the default 'appsettings.json' are read.
|
||||
Browser.Equal("Default key1-value", () => _appElement.FindElement(By.Id("key1")).Text);
|
||||
|
||||
// Verify values overriden by an environment specific 'appsettings.$(Environment).json are read
|
||||
Assert.Equal("Development key2-value", _appElement.FindElement(By.Id("key2")).Text);
|
||||
|
||||
// Lastly for sanity, make sure values specified in an environment specific 'appsettings.$(Environment).json are read
|
||||
Assert.Equal("Development key3-value", _appElement.FindElement(By.Id("key3")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WebAssemblyHostingEnvironment_Works()
|
||||
{
|
||||
// Dev-Server defaults to Development. It's in the name!
|
||||
Browser.Equal("Development", () => _appElement.FindElement(By.Id("environment")).Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
@inject Microsoft.Extensions.Configuration.IConfiguration Config
|
||||
@inject Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment HostEnvironment
|
||||
|
||||
<ul>
|
||||
<li id="key1">@Config["key1"]</li>
|
||||
<li id="key2">@Config["key2"]</li>
|
||||
<li id="key3">@Config["key3"]</li>
|
||||
</ul>
|
||||
|
||||
<div id="environment">@HostEnvironment.Environment</div>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
<option value="BasicTestApp.CascadingValueTest.CascadingValueSupplier">Cascading values</option>
|
||||
<option value="BasicTestApp.ComponentRefComponent">Component ref component</option>
|
||||
<option value="BasicTestApp.ConcurrentRenderParent">Concurrent rendering</option>
|
||||
<option value="BasicTestApp.ConfigurationComponent">Configuration</option>
|
||||
<option value="BasicTestApp.CounterComponent">Counter</option>
|
||||
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
|
||||
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
|
||||
|
|
@ -28,12 +29,12 @@
|
|||
<option value="BasicTestApp.EventPreventDefaultComponent">Event preventDefault</option>
|
||||
<option value="BasicTestApp.ExternalContentPackage">External content package</option>
|
||||
<option value="BasicTestApp.FocusEventComponent">Focus events</option>
|
||||
<option value="BasicTestApp.FormsTest.ExperimentalValidationComponent">Experimental validation</option>
|
||||
<option value="BasicTestApp.FormsTest.NotifyPropertyChangedValidationComponent">INotifyPropertyChanged validation</option>
|
||||
<option value="BasicTestApp.FormsTest.SimpleValidationComponent">Simple validation</option>
|
||||
<option value="BasicTestApp.FormsTest.SimpleValidationComponentUsingExperimentalValidator">Simple validation using experimental validator</option>
|
||||
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
|
||||
<option value="BasicTestApp.FormsTest.TypicalValidationComponentUsingExperimentalValidator">Typical validation using experimental validator</option>
|
||||
<option value="BasicTestApp.FormsTest.ExperimentalValidationComponent">Experimental validation</option>
|
||||
<option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option>
|
||||
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
|
||||
<option value="BasicTestApp.HtmlBlockChildContent">ChildContent HTML Block</option>
|
||||
|
|
@ -60,10 +61,10 @@
|
|||
<option value="BasicTestApp.ParentChildComponent">Parent component with child</option>
|
||||
<option value="BasicTestApp.PropertiesChangedHandlerParent">Parent component that changes parameters on child</option>
|
||||
<option value="BasicTestApp.RazorTemplates">Razor Templates</option>
|
||||
<option value="BasicTestApp.Reconnection.ReconnectionComponent">Reconnection server-side blazor</option>
|
||||
<option value="BasicTestApp.RedTextComponent">Red text</option>
|
||||
<option value="BasicTestApp.ReliabilityComponent">Server reliability component</option>
|
||||
<option value="BasicTestApp.RenderFragmentToggler">Render fragment renderer</option>
|
||||
<option value="BasicTestApp.Reconnection.ReconnectionComponent">Reconnection server-side blazor</option>
|
||||
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
||||
<option value="BasicTestApp.RouterTest.NavigationManagerComponent">NavigationManager Test</option>
|
||||
<option value="BasicTestApp.RouterTest.TestRouter">Router</option>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"key2": "Development key2-value",
|
||||
"key3": "Development key3-value"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"key2": "Prod key2-value",
|
||||
"key3": "Prod key3-value"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"key1": "Default key1-value",
|
||||
"key2": "Default key2-value"
|
||||
}
|
||||
Loading…
Reference in New Issue