Add support for configuration (#19544)

* Add support for configuration

Fixes https://github.com/dotnet/aspnetcore/issues/18675
This commit is contained in:
Pranav K 2020-03-12 11:38:06 -07:00 committed by GitHub
parent 0006fdf093
commit 147c39289a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 384 additions and 98 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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) {

View File

@ -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 };

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}

View File

@ -1,4 +1,4 @@
import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader";
import { WebAssemblyResourceLoader } from './WebAssemblyResourceLoader';
export interface Platform {
start(resourceLoader: WebAssemblyResourceLoader): Promise<void>;

View File

@ -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());
}
}
}

View File

@ -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 };

View File

@ -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

View File

@ -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

View File

@ -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)" />

View File

@ -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",

View File

@ -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,

View File

@ -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();
});

View File

@ -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;

View File

@ -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; }
}
}

View File

@ -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);

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,4 @@
{
"key2": "Development key2-value",
"key3": "Development key3-value"
}

View File

@ -0,0 +1,4 @@
{
"key2": "Prod key2-value",
"key3": "Prod key3-value"
}

View File

@ -0,0 +1,4 @@
{
"key1": "Default key1-value",
"key2": "Default key2-value"
}