diff --git a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.csproj b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.csproj index 88ca078caa..25d2991601 100644 --- a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.csproj +++ b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.csproj @@ -7,6 +7,6 @@ - + diff --git a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.1.cs b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.1.cs index b8062d72c0..eb33362975 100644 --- a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.1.cs +++ b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.1.cs @@ -12,38 +12,38 @@ namespace Microsoft.AspNetCore.Blazor } namespace Microsoft.AspNetCore.Blazor.Hosting { - public static partial class BlazorWebAssemblyHost + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct RootComponentMapping { - public static Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder CreateDefaultBuilder() { throw null; } + private readonly object _dummy; + public RootComponentMapping(System.Type componentType, string selector) { throw null; } + public System.Type ComponentType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Selector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } - public partial interface IWebAssemblyHost : System.IDisposable + public partial class RootComponentMappingCollection : System.Collections.ObjectModel.Collection { - System.IServiceProvider Services { get; } - System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public RootComponentMappingCollection() { } + public void Add(System.Type componentType, string selector) { } + public void AddRange(System.Collections.Generic.IEnumerable items) { } + public void Add(string selector) where TComponent : Microsoft.AspNetCore.Components.IComponent { } } - public partial interface IWebAssemblyHostBuilder + public sealed partial class WebAssemblyHost : System.IAsyncDisposable { - System.Collections.Generic.IDictionary Properties { get; } - Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHost Build(); - Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder ConfigureServices(System.Action configureDelegate); - Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder UseServiceProviderFactory(Microsoft.Extensions.DependencyInjection.IServiceProviderFactory factory); - Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder UseServiceProviderFactory(System.Func> factory); + internal WebAssemblyHost() { } + public Microsoft.Extensions.Configuration.IConfiguration Configuration { get { throw null; } } + public System.IServiceProvider Services { get { throw null; } } + [System.Diagnostics.DebuggerStepThroughAttribute] + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + public System.Threading.Tasks.Task RunAsync() { throw null; } } - public sealed partial class WebAssemblyHostBuilderContext + public sealed partial class WebAssemblyHostBuilder { - public WebAssemblyHostBuilderContext(System.Collections.Generic.IDictionary properties) { } - public System.Collections.Generic.IDictionary Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } - public static partial class WebAssemblyHostBuilderExtensions - { - public static Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder ConfigureServices(this Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } - public static Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder UseBlazorStartup(this Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder builder, System.Type startupType) { throw null; } - public static Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder UseBlazorStartup(this Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHostBuilder builder) { throw null; } - } - public static partial class WebAssemblyHostExtensions - { - public static void Run(this Microsoft.AspNetCore.Blazor.Hosting.IWebAssemblyHost host) { } + internal WebAssemblyHostBuilder() { } + public Microsoft.Extensions.Configuration.IConfigurationBuilder Configuration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Blazor.Hosting.RootComponentMappingCollection RootComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.DependencyInjection.IServiceCollection Services { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Blazor.Hosting.WebAssemblyHost Build() { throw null; } + public static Microsoft.AspNetCore.Blazor.Hosting.WebAssemblyHostBuilder CreateDefault(string[] args = null) { throw null; } } } namespace Microsoft.AspNetCore.Blazor.Http @@ -67,15 +67,3 @@ namespace Microsoft.AspNetCore.Blazor.Rendering public static System.Threading.Tasks.Task DispatchEvent(Microsoft.AspNetCore.Components.RenderTree.WebEventDescriptor eventDescriptor, string eventArgsJson) { throw null; } } } -namespace Microsoft.AspNetCore.Components.Builder -{ - public static partial class ComponentsApplicationBuilderExtensions - { - public static void AddComponent(this Microsoft.AspNetCore.Components.Builder.IComponentsApplicationBuilder app, string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { } - } - public partial interface IComponentsApplicationBuilder - { - System.IServiceProvider Services { get; } - void AddComponent(System.Type componentType, string domElementSelector); - } -} diff --git a/src/Components/Blazor/Blazor/src/Builder/ComponentsApplicationBuilderExtensions.cs b/src/Components/Blazor/Blazor/src/Builder/ComponentsApplicationBuilderExtensions.cs deleted file mode 100644 index 66532474c6..0000000000 --- a/src/Components/Blazor/Blazor/src/Builder/ComponentsApplicationBuilderExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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.Builder -{ - /// - /// Provides extension methods for . - /// - public static class ComponentsApplicationBuilderExtensions - { - /// - /// Associates the component type with the application, - /// causing it to be displayed in the specified DOM element. - /// - /// The . - /// The type of the component. - /// A CSS selector that uniquely identifies a DOM element. - public static void AddComponent(this IComponentsApplicationBuilder app, string domElementSelector) - where TComponent : IComponent - { - app.AddComponent(typeof(TComponent), domElementSelector); - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Builder/IComponentsApplicationBuilder.cs b/src/Components/Blazor/Blazor/src/Builder/IComponentsApplicationBuilder.cs deleted file mode 100644 index 84806a8769..0000000000 --- a/src/Components/Blazor/Blazor/src/Builder/IComponentsApplicationBuilder.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 System; - -namespace Microsoft.AspNetCore.Components.Builder -{ - /// - /// A builder for adding components to an application. - /// - public interface IComponentsApplicationBuilder - { - /// - /// Gets the application services. - /// - IServiceProvider Services { get; } - - /// - /// Associates the with the application, - /// causing it to be displayed in the specified DOM element. - /// - /// The type of the component. - /// A CSS selector that uniquely identifies a DOM element. - void AddComponent(Type componentType, string domElementSelector); - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/BlazorWebAssemblyHost.cs b/src/Components/Blazor/Blazor/src/Hosting/BlazorWebAssemblyHost.cs deleted file mode 100644 index e5631805ef..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/BlazorWebAssemblyHost.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Blazor.Hosting -{ - /// - /// Used to create an instance of Blazor host builder for a Browser application. - /// - public static class BlazorWebAssemblyHost - { - /// - /// Creates an instance of . - /// - /// The . - public static IWebAssemblyHostBuilder CreateDefaultBuilder() - { - return new WebAssemblyHostBuilder(); - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/ConventionBasedStartup.cs b/src/Components/Blazor/Blazor/src/Hosting/ConventionBasedStartup.cs deleted file mode 100644 index 53eecbf6e0..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/ConventionBasedStartup.cs +++ /dev/null @@ -1,117 +0,0 @@ -// 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 System; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.ExceptionServices; -using Microsoft.AspNetCore.Components.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - // Keeping this simple for now to focus on predictable and reasonable behaviors. - // Startup in WebHost supports lots of things we don't yet support, and some we - // may never support. - // - // Possible additions: - // - environments - // - case-insensitivity (makes sense with environments) - // - // Likely never: - // - statics - // - DI into constructor - internal class ConventionBasedStartup : IBlazorStartup - { - public ConventionBasedStartup(object instance) - { - Instance = instance ?? throw new ArgumentNullException(nameof(instance)); - } - - public object Instance { get; } - - public void Configure(IComponentsApplicationBuilder app, IServiceProvider services) - { - try - { - var method = GetConfigureMethod(); - Debug.Assert(method != null); - - var parameters = method.GetParameters(); - var arguments = new object[parameters.Length]; - for (var i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - arguments[i] = parameter.ParameterType == typeof(IComponentsApplicationBuilder) - ? app - : services.GetRequiredService(parameter.ParameterType); - } - - method.Invoke(Instance, arguments); - } - catch (Exception ex) - { - if (ex is TargetInvocationException) - { - ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); - } - - throw; - } - } - - internal MethodInfo GetConfigureMethod() - { - var methods = Instance.GetType() - .GetMethods(BindingFlags.Instance | BindingFlags.Public) - .Where(m => string.Equals(m.Name, "Configure", StringComparison.Ordinal)) - .ToArray(); - - if (methods.Length == 1) - { - return methods[0]; - } - else if (methods.Length == 0) - { - throw new InvalidOperationException("The startup class must define a 'Configure' method."); - } - else - { - throw new InvalidOperationException("Overloading the 'Configure' method is not supported."); - } - } - - public void ConfigureServices(IServiceCollection services) - { - try - { - var method = GetConfigureServicesMethod(); - if (method != null) - { - method.Invoke(Instance, new object[] { services }); - } - } - catch (Exception ex) - { - if (ex is TargetInvocationException) - { - ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); - } - - throw; - } - } - - internal MethodInfo GetConfigureServicesMethod() - { - return Instance.GetType() - .GetMethod( - "ConfigureServices", - BindingFlags.Public | BindingFlags.Instance, - null, - new Type[] { typeof(IServiceCollection), }, - Array.Empty()); - } - } -} \ No newline at end of file diff --git a/src/Components/Blazor/Blazor/src/Hosting/IBlazorStartup.cs b/src/Components/Blazor/Blazor/src/Hosting/IBlazorStartup.cs deleted file mode 100644 index e87138387f..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/IBlazorStartup.cs +++ /dev/null @@ -1,16 +0,0 @@ -// 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 System; -using Microsoft.AspNetCore.Components.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - internal interface IBlazorStartup - { - void ConfigureServices(IServiceCollection services); - - void Configure(IComponentsApplicationBuilder app, IServiceProvider services); - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyHost.cs b/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyHost.cs deleted file mode 100644 index 8f21f93421..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyHost.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - /// - /// A program abstraction. - /// - public interface IWebAssemblyHost : IDisposable - { - /// - /// The programs configured services. - /// - IServiceProvider Services { get; } - - /// - /// Start the program. - /// - /// Used to abort program start. - /// - Task StartAsync(CancellationToken cancellationToken = default); - - /// - /// Attempts to gracefully stop the program. - /// - /// Used to indicate when stop should no longer be graceful. - /// - Task StopAsync(CancellationToken cancellationToken = default); - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyHostBuilder.cs b/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyHostBuilder.cs deleted file mode 100644 index c5a52bc280..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyHostBuilder.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - /// - /// Abstraction for configuring a Blazor browser-based application. - /// - public interface IWebAssemblyHostBuilder - { - /// - /// A central location for sharing state between components during the host building process. - /// - IDictionary Properties { get; } - - /// - /// Overrides the factory used to create the service provider. - /// - /// The same instance of the for chaining. - IWebAssemblyHostBuilder UseServiceProviderFactory(IServiceProviderFactory factory); - - /// - /// Overrides the factory used to create the service provider. - /// - /// The same instance of the for chaining. - IWebAssemblyHostBuilder UseServiceProviderFactory(Func> factory); - - /// - /// Adds services to the container. This can be called multiple times and the results will be additive. - /// - /// The delegate for configuring the that will be used - /// to construct the . - /// The same instance of the for chaining. - IWebAssemblyHostBuilder ConfigureServices(Action configureDelegate); - - /// - /// Run the given actions to initialize the host. This can only be called once. - /// - /// An initialized - IWebAssemblyHost Build(); - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyServiceFactoryAdapter.cs b/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyServiceFactoryAdapter.cs deleted file mode 100644 index c790a3c879..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyServiceFactoryAdapter.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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 System; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - // Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/IServiceFactoryAdapter.cs - - internal interface IWebAssemblyServiceFactoryAdapter - { - object CreateBuilder(IServiceCollection services); - - IServiceProvider CreateServiceProvider(object containerBuilder); - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/RootComponentMapping.cs b/src/Components/Blazor/Blazor/src/Hosting/RootComponentMapping.cs new file mode 100644 index 0000000000..53b34d8822 --- /dev/null +++ b/src/Components/Blazor/Blazor/src/Hosting/RootComponentMapping.cs @@ -0,0 +1,53 @@ +// 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 System; +using Microsoft.AspNetCore.Components; + +namespace Microsoft.AspNetCore.Blazor.Hosting +{ + /// + /// Defines a mapping between a root and a DOM element selector. + /// + public readonly struct RootComponentMapping + { + /// + /// Creates a new instance of with the provided + /// and . + /// + /// The component type. Must implement . + /// The DOM element selector. + public RootComponentMapping(Type componentType, string selector) + { + if (componentType is null) + { + throw new ArgumentNullException(nameof(componentType)); + } + + if (!typeof(IComponent).IsAssignableFrom(componentType)) + { + throw new ArgumentException( + $"The type '{componentType.Name}' must implement {nameof(IComponent)} to be used as a root component.", + nameof(componentType)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + ComponentType = componentType; + Selector = selector; + } + + /// + /// Gets the component type. + /// + public Type ComponentType { get; } + + /// + /// Gets the DOM element selector. + /// + public string Selector { get; } + } +} diff --git a/src/Components/Blazor/Blazor/src/Hosting/RootComponentMappingCollection.cs b/src/Components/Blazor/Blazor/src/Hosting/RootComponentMappingCollection.cs new file mode 100644 index 0000000000..0e488f0deb --- /dev/null +++ b/src/Components/Blazor/Blazor/src/Hosting/RootComponentMappingCollection.cs @@ -0,0 +1,68 @@ +// 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 System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.AspNetCore.Components; + +namespace Microsoft.AspNetCore.Blazor.Hosting +{ + /// + /// Defines a collection of items. + /// + public class RootComponentMappingCollection : Collection + { + /// + /// Adds a component mapping to the collection. + /// + /// The component type. + /// The DOM element selector. + public void Add(string selector) where TComponent : IComponent + { + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + Add(new RootComponentMapping(typeof(TComponent), selector)); + } + + /// + /// Adds a component mapping to the collection. + /// + /// The component type. Must implement . + /// The DOM element selector. + public void Add(Type componentType, string selector) + { + if (componentType is null) + { + throw new ArgumentNullException(nameof(componentType)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + Add(new RootComponentMapping(componentType, selector)); + } + + /// + /// Adds a collection of items to this collection. + /// + /// The items to add. + public void AddRange(IEnumerable items) + { + if (items is null) + { + throw new ArgumentNullException(nameof(items)); + } + + foreach (var item in items) + { + Add(item); + } + } + } +} diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyBlazorApplicationBuilder.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyBlazorApplicationBuilder.cs deleted file mode 100644 index 8cb5d87561..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyBlazorApplicationBuilder.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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 System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Blazor.Rendering; -using Microsoft.AspNetCore.Components.Builder; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - internal class WebAssemblyBlazorApplicationBuilder : IComponentsApplicationBuilder - { - public WebAssemblyBlazorApplicationBuilder(IServiceProvider services) - { - Entries = new List<(Type componentType, string domElementSelector)>(); - Services = services; - } - - public List<(Type componentType, string domElementSelector)> Entries { get; } - - public IServiceProvider Services { get; } - - public void AddComponent(Type componentType, string domElementSelector) - { - if (componentType == null) - { - throw new ArgumentNullException(nameof(componentType)); - } - - if (domElementSelector == null) - { - throw new ArgumentNullException(nameof(domElementSelector)); - } - - Entries.Add((componentType, domElementSelector)); - } - - public async Task CreateRendererAsync() - { - var loggerFactory = (ILoggerFactory)Services.GetService(typeof(ILoggerFactory)); - var renderer = new WebAssemblyRenderer(Services, loggerFactory); - for (var i = 0; i < Entries.Count; i++) - { - var (componentType, domElementSelector) = Entries[i]; - await renderer.AddComponentAsync(componentType, domElementSelector); - } - - return renderer; - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs index b90878fdde..02c2693ff7 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs @@ -5,94 +5,135 @@ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Blazor.Rendering; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.JSInterop; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Blazor.Hosting { - internal class WebAssemblyHost : IWebAssemblyHost + /// + /// A host object for Blazor running under WebAssembly. Use + /// to initialize a . + /// + public sealed class WebAssemblyHost : IAsyncDisposable { - private readonly IJSRuntime _runtime; + private readonly IServiceScope _scope; + private readonly IServiceProvider _services; + private readonly IConfiguration _configuration; + private readonly RootComponentMapping[] _rootComponents; - private IServiceScope _scope; + // NOTE: the host is disposable because it OWNs references to disposable things. + // + // The twist is that in general dispose is not going to run even if the user puts it in a using. + // When a user refreshes or navigates away that terminates the app, like a process.exit. So the + // dispose functionality here is basically so that it can be used in unit tests. + // + // Based on the APIs that exist in Blazor today it's not possible for the + // app to get disposed, however if we add something like that in the future, most of the work is + // already done. + private bool _disposed; + private bool _started; private WebAssemblyRenderer _renderer; - public WebAssemblyHost(IServiceProvider services, IJSRuntime runtime) + internal WebAssemblyHost(IServiceProvider services, IServiceScope scope, IConfiguration configuration, RootComponentMapping[] rootComponents) { // To ensure JS-invoked methods don't get linked out, have a reference to their enclosing types GC.KeepAlive(typeof(EntrypointInvoker)); GC.KeepAlive(typeof(JSInteropMethods)); GC.KeepAlive(typeof(WebAssemblyEventDispatcher)); - Services = services ?? throw new ArgumentNullException(nameof(services)); - _runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); + _services = services; + _scope = scope; + _configuration = configuration; + _rootComponents = rootComponents; } - public IServiceProvider Services { get; } + /// + /// Gets the application configuration. + /// + public IConfiguration Configuration => _configuration; - public Task StartAsync(CancellationToken cancellationToken = default) + /// + /// Gets the service provider associated with the application. + /// + public IServiceProvider Services => _scope.ServiceProvider; + + /// + /// Disposes the host asynchronously. + /// + /// A which respresents the completion of disposal. + public async ValueTask DisposeAsync() { - return StartAsyncAwaited(); - } - - private async Task StartAsyncAwaited() - { - var scopeFactory = Services.GetRequiredService(); - _scope = scopeFactory.CreateScope(); - - try + if (_disposed) { - var startup = _scope.ServiceProvider.GetService(); - if (startup == null) + return; + } + + _disposed = true; + + _renderer?.Dispose(); + + if (_scope is IAsyncDisposable asyncDisposableScope) + { + await asyncDisposableScope.DisposeAsync(); + } + else + { + _scope?.Dispose(); + } + + if (_services is IAsyncDisposable asyncDisposableServices) + { + await asyncDisposableServices.DisposeAsync(); + } + else if (_services is IDisposable disposableServices) + { + disposableServices.Dispose(); + } + } + + /// + /// Runs the application associated with this host. + /// + /// A which represents exit of the application. + /// + /// At this time, it's not possible to shut down a Blazor WebAssembly application using imperative code. + /// The application only stops when the hosting page is reloaded or navigated to another page. As a result + /// the task returned from this method does not complete. This method is not suitable for use in unit-testing. + /// + public Task RunAsync() + { + // RunAsyncCore will await until the CancellationToken fires. However, we don't fire it + // currently, so the app will "run" forever. + return RunAsyncCore(CancellationToken.None); + } + + // Internal for testing. + internal async Task RunAsyncCore(CancellationToken cancellationToken) + { + if (_started) + { + throw new InvalidOperationException("The host has already started."); + } + + _started = true; + + var tcs = new TaskCompletionSource(); + + using (cancellationToken.Register(() => { tcs.TrySetResult(null); })) + { + var loggerFactory = Services.GetRequiredService(); + _renderer = new WebAssemblyRenderer(Services, loggerFactory); + + var rootComponents = _rootComponents; + for (var i = 0; i < rootComponents.Length; i++) { - var message = - $"Could not find a registered Blazor Startup class. " + - $"Using {nameof(IWebAssemblyHost)} requires a call to {nameof(IWebAssemblyHostBuilder)}.UseBlazorStartup."; - throw new InvalidOperationException(message); + var rootComponent = rootComponents[i]; + await _renderer.AddComponentAsync(rootComponent.ComponentType, rootComponent.Selector); } - // Note that we differ from the WebHost startup path here by using a 'scope' for the app builder - // as well as the Configure method. - var builder = new WebAssemblyBlazorApplicationBuilder(_scope.ServiceProvider); - startup.Configure(builder, _scope.ServiceProvider); - - _renderer = await builder.CreateRendererAsync(); + await tcs.Task; } - catch - { - _scope.Dispose(); - _scope = null; - - if (_renderer != null) - { - _renderer.Dispose(); - _renderer = null; - } - - throw; - } - } - - public Task StopAsync(CancellationToken cancellationToken = default) - { - if (_scope != null) - { - _scope.Dispose(); - _scope = null; - } - - if (_renderer != null) - { - _renderer.Dispose(); - _renderer = null; - } - - return Task.CompletedTask; - } - - public void Dispose() - { - (Services as IDisposable)?.Dispose(); } } } diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs index 7fa4dd0ae1..1d1f05dce7 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using Microsoft.AspNetCore.Blazor.Services; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; @@ -14,87 +16,89 @@ using Microsoft.JSInterop; namespace Microsoft.AspNetCore.Blazor.Hosting { - // - // This code was taken virtually as-is from the Microsoft.Extensions.Hosting project in aspnet/Hosting and then - // lots of things were removed. - // - internal class WebAssemblyHostBuilder : IWebAssemblyHostBuilder + /// + /// A builder for configuring and creating a . + /// + public sealed class WebAssemblyHostBuilder { - private List> _configureServicesActions = new List>(); - private bool _hostBuilt; - private WebAssemblyHostBuilderContext _BrowserHostBuilderContext; - private IWebAssemblyServiceFactoryAdapter _serviceProviderFactory = new WebAssemblyServiceFactoryAdapter(new DefaultServiceProviderFactory()); - private IServiceProvider _appServices; - /// - /// A central location for sharing state between components during the host building process. + /// Creates an instance of using the most common + /// conventions and settings. /// - public IDictionary Properties { get; } = new Dictionary(); - - /// - /// Adds services to the container. This can be called multiple times and the results will be additive. - /// - /// - /// The same instance of the for chaining. - public IWebAssemblyHostBuilder ConfigureServices(Action configureDelegate) + /// The argument passed to the application's main method. + /// A . + public static WebAssemblyHostBuilder CreateDefault(string[] args = default) { - _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); - return this; + // We don't use the args for anything right now, but we want to accept them + // here so that it shows up this way in the project templates. + args ??= Array.Empty(); + var builder = new WebAssemblyHostBuilder(); + + // Right now we don't have conventions or behaviors that are specific to this method + // however, making this the default for the template allows us to add things like that + // in the future, while giving `new WebAssemblyHostBuilder` as an opt-out of opinionated + // settings. + return builder; } /// - /// Overrides the factory used to create the service provider. + /// Creates an instance of with the minimal configuration. /// - /// The same instance of the for chaining. - public IWebAssemblyHostBuilder UseServiceProviderFactory(IServiceProviderFactory factory) + private WebAssemblyHostBuilder() { - _serviceProviderFactory = new WebAssemblyServiceFactoryAdapter(factory ?? throw new ArgumentNullException(nameof(factory))); - return this; + // Private right now because we don't have much reason to expose it. This can be exposed + // in the future if we want to give people a choice between CreateDefault and something + // less opinionated. + Configuration = new ConfigurationBuilder(); + RootComponents = new RootComponentMappingCollection(); + Services = new ServiceCollection(); + + InitializeDefaultServices(); } /// - /// Overrides the factory used to create the service provider. + /// Gets an that can be used to customize the application's + /// configuration sources. /// - /// The same instance of the for chaining. - public IWebAssemblyHostBuilder UseServiceProviderFactory(Func> factory) - { - _serviceProviderFactory = new WebAssemblyServiceFactoryAdapter(() => _BrowserHostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory))); - return this; - } + public IConfigurationBuilder Configuration { get; } /// - /// Run the given actions to initialize the host. This can only be called once. + /// Gets the collection of root component mappings configured for the application. /// - /// An initialized - public IWebAssemblyHost Build() + public RootComponentMappingCollection RootComponents { get; } + + /// + /// Gets the service collection. + /// + public IServiceCollection Services { get; } + + /// + /// Builds a instance based on the configuration of this builder. + /// + /// A object. + public WebAssemblyHost Build() { - if (_hostBuilt) - { - throw new InvalidOperationException("Build can only be called once."); - } - _hostBuilt = true; + // Intentionally overwrite configuration with the one we're creating. + var configuration = Configuration.Build(); + Services.AddSingleton(configuration); - CreateBrowserHostBuilderContext(); - CreateServiceProvider(); + // A Blazor application always runs in a scope. Since we want to make it possible for the user + // to configure services inside *that scope* inside their startup code, we create *both* the + // service provider and the scope here. + var services = Services.BuildServiceProvider(); + var scope = services.GetRequiredService().CreateScope(); - return _appServices.GetRequiredService(); + return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray()); } - private void CreateBrowserHostBuilderContext() + private void InitializeDefaultServices() { - _BrowserHostBuilderContext = new WebAssemblyHostBuilderContext(Properties); - } - - private void CreateServiceProvider() - { - var services = new ServiceCollection(); - services.AddSingleton(_BrowserHostBuilderContext); - services.AddSingleton(); - services.AddSingleton(WebAssemblyJSRuntime.Instance); - services.AddSingleton(WebAssemblyNavigationManager.Instance); - services.AddSingleton(WebAssemblyNavigationInterception.Instance); - services.AddSingleton(); - services.AddSingleton(s => + Services.AddSingleton(WebAssemblyJSRuntime.Instance); + Services.AddSingleton(WebAssemblyNavigationManager.Instance); + Services.AddSingleton(WebAssemblyNavigationInterception.Instance); + Services.AddSingleton(); + Services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(WebAssemblyConsoleLogger<>))); + Services.AddSingleton(s => { // Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it. var navigationManager = s.GetRequiredService(); @@ -103,20 +107,6 @@ namespace Microsoft.AspNetCore.Blazor.Hosting BaseAddress = new Uri(navigationManager.BaseUri) }; }); - - // Needed for authorization - // However, since authorization isn't on by default, we could consider removing these and - // having a separate services.AddBlazorAuthorization() call that brings in the required services. - services.AddOptions(); - services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(WebAssemblyConsoleLogger<>))); - - foreach (var configureServicesAction in _configureServicesActions) - { - configureServicesAction(_BrowserHostBuilderContext, services); - } - - var builder = _serviceProviderFactory.CreateBuilder(services); - _appServices = _serviceProviderFactory.CreateServiceProvider(builder); } } } diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilderContext.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilderContext.cs deleted file mode 100644 index c7b7dd6f19..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilderContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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 System.Collections.Generic; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - /// - /// Context containing the common services on the . Some properties may be null until set by the . - /// - public sealed class WebAssemblyHostBuilderContext - { - /// - /// Creates a new . - /// - /// The property collection. - public WebAssemblyHostBuilderContext(IDictionary properties) - { - Properties = properties ?? throw new System.ArgumentNullException(nameof(properties)); - } - - /// - /// A central location for sharing state between components during the host building process. - /// - public IDictionary Properties { get; } - } -} \ No newline at end of file diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilderExtensions.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilderExtensions.cs deleted file mode 100644 index 9b03c09766..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilderExtensions.cs +++ /dev/null @@ -1,73 +0,0 @@ -// 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 System; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - /// - /// Provides Blazor-specific support for . - /// - public static class WebAssemblyHostBuilderExtensions - { - private const string BlazorStartupKey = "Blazor.Startup"; - - /// - /// Adds services to the container. This can be called multiple times and the results will be additive. - /// - /// The to configure. - /// - /// The same instance of the for chaining. - public static IWebAssemblyHostBuilder ConfigureServices(this IWebAssemblyHostBuilder hostBuilder, Action configureDelegate) - { - return hostBuilder.ConfigureServices((context, collection) => configureDelegate(collection)); - } - - /// - /// Configures the to use the provided startup class. - /// - /// The . - /// A type that configures a Blazor application. - /// The . - public static IWebAssemblyHostBuilder UseBlazorStartup(this IWebAssemblyHostBuilder builder, Type startupType) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (builder.Properties.ContainsKey(BlazorStartupKey)) - { - throw new InvalidOperationException("A startup class has already been registered."); - } - - // It would complicate the implementation to allow multiple startup classes, and we don't - // really have a need for it. - builder.Properties.Add(BlazorStartupKey, bool.TrueString); - - var startup = new ConventionBasedStartup(Activator.CreateInstance(startupType)); - - builder.ConfigureServices(startup.ConfigureServices); - builder.ConfigureServices(s => s.AddSingleton(startup)); - - return builder; - } - - /// - /// Configures the to use the provided startup class. - /// - /// A type that configures a Blazor application. - /// The . - /// The . - public static IWebAssemblyHostBuilder UseBlazorStartup(this IWebAssemblyHostBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return UseBlazorStartup(builder, typeof(TStartup)); - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs deleted file mode 100644 index d08162a590..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 System; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - /// - /// Extension methods for . - /// - public static class WebAssemblyHostExtensions - { - /// - /// Runs the application. - /// - /// The to run. - /// - /// Currently, Blazor applications running in the browser don't have a lifecycle - the application does not - /// get a chance to gracefully shut down. For now, simply starts the host - /// and allows execution to continue. - /// - public static void Run(this IWebAssemblyHost host) - { - // Behave like async void, because we don't yet support async-main properly on WebAssembly. - // However, don't actualy make this method async, because we rely on startup being synchronous - // for things like attaching navigation event handlers. - host.StartAsync().ContinueWith(task => - { - if (task.Exception != null) - { - Console.WriteLine(task.Exception); - } - }); - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyServiceFactoryAdapter.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyServiceFactoryAdapter.cs deleted file mode 100644 index fcc879653a..0000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyServiceFactoryAdapter.cs +++ /dev/null @@ -1,52 +0,0 @@ -// 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 System; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - // Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/ServiceFactoryAdapter.cs - - internal class WebAssemblyServiceFactoryAdapter : IWebAssemblyServiceFactoryAdapter - { - private IServiceProviderFactory _serviceProviderFactory; - private readonly Func _contextResolver; - private Func> _factoryResolver; - - public WebAssemblyServiceFactoryAdapter(IServiceProviderFactory serviceProviderFactory) - { - _serviceProviderFactory = serviceProviderFactory ?? throw new ArgumentNullException(nameof(serviceProviderFactory)); - } - - public WebAssemblyServiceFactoryAdapter(Func contextResolver, Func> factoryResolver) - { - _contextResolver = contextResolver ?? throw new ArgumentNullException(nameof(contextResolver)); - _factoryResolver = factoryResolver ?? throw new ArgumentNullException(nameof(factoryResolver)); - } - - public object CreateBuilder(IServiceCollection services) - { - if (_serviceProviderFactory == null) - { - _serviceProviderFactory = _factoryResolver(_contextResolver()); - - if (_serviceProviderFactory == null) - { - throw new InvalidOperationException("The resolver returned a null IServiceProviderFactory"); - } - } - return _serviceProviderFactory.CreateBuilder(services); - } - - public IServiceProvider CreateServiceProvider(object containerBuilder) - { - if (_serviceProviderFactory == null) - { - throw new InvalidOperationException("CreateBuilder must be called before CreateServiceProvider"); - } - - return _serviceProviderFactory.CreateServiceProvider((TContainerBuilder)containerBuilder); - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj b/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj index be94e420a2..ff30d9d9c8 100644 --- a/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj +++ b/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 @@ -9,7 +9,7 @@ - + diff --git a/src/Components/Blazor/Blazor/test/Hosting/ConventionBasedStartupTest.cs b/src/Components/Blazor/Blazor/test/Hosting/ConventionBasedStartupTest.cs deleted file mode 100644 index cbc73b79f8..0000000000 --- a/src/Components/Blazor/Blazor/test/Hosting/ConventionBasedStartupTest.cs +++ /dev/null @@ -1,210 +0,0 @@ -// 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 System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Blazor.Hosting; -using Microsoft.AspNetCore.Components.Builder; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.AspNetCore.Components.Hosting -{ - public class ConventionBasedStartupTest - { - [Fact] - public void ConventionBasedStartup_GetConfigureServicesMethod_FindsConfigureServices() - { - // Arrange - var startup = new ConventionBasedStartup(new MyStartup1()); - - // Act - var method = startup.GetConfigureServicesMethod(); - - // Assert - Assert.Equal(typeof(IServiceCollection), method.GetParameters()[0].ParameterType); - } - - private class MyStartup1 - { - public void ConfigureServices(IServiceCollection services) - { - } - - // Ignored - public void ConfigureServices(DateTime x) - { - - } - - // Ignored - private void ConfigureServices(int x) - { - } - - // Ignored - public static void ConfigureServices(string x) - { - } - } - - [Fact] - public void ConventionBasedStartup_GetConfigureServicesMethod_NoMethodFound() - { - // Arrange - var startup = new ConventionBasedStartup(new MyStartup2()); - - // Act - var method = startup.GetConfigureServicesMethod(); - - // Assert - Assert.Null(method); - } - - private class MyStartup2 - { - } - - [Fact] - public void ConventionBasedStartup_ConfigureServices_CallsMethod() - { - // Arrange - var startup = new ConventionBasedStartup(new MyStartup3()); - var services = new ServiceCollection(); - - // Act - startup.ConfigureServices(services); - - // Assert - Assert.NotEmpty(services); - } - - private class MyStartup3 - { - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton("foo"); - } - } - - [Fact] - public void ConventionBasedStartup_ConfigureServices_NoMethodFound() - { - // Arrange - var startup = new ConventionBasedStartup(new MyStartup4()); - var services = new ServiceCollection(); - - // Act - startup.ConfigureServices(services); - - // Assert - Assert.Empty(services); - } - - private class MyStartup4 - { - } - - [Fact] - public void ConventionBasedStartup_GetConfigureMethod_FindsConfigure() - { - // Arrange - var startup = new ConventionBasedStartup(new MyStartup5()); - - // Act - var method = startup.GetConfigureMethod(); - - // Assert - Assert.Empty(method.GetParameters()); - } - - private class MyStartup5 - { - public void Configure() - { - } - - // Ignored - private void Configure(int x) - { - } - - // Ignored - public static void Configure(string x) - { - } - } - - [Fact] - public void ConventionBasedStartup_GetConfigureMethod_NoMethodFoundThrows() - { - // Arrange - var startup = new ConventionBasedStartup(new MyStartup6()); - - // Act - var ex = Assert.Throws(() => startup.GetConfigureMethod()); - - // Assert - Assert.Equal("The startup class must define a 'Configure' method.", ex.Message); - } - - private class MyStartup6 - { - } - - [Fact] - public void ConventionBasedStartup_GetConfigureMethod_OverloadedThrows() - { - // Arrange - var startup = new ConventionBasedStartup(new MyStartup7()); - - // Act - var ex = Assert.Throws(() => startup.GetConfigureMethod()); - - // Assert - Assert.Equal("Overloading the 'Configure' method is not supported.", ex.Message); - } - - private class MyStartup7 - { - public void Configure() - { - } - - public void Configure(string x) - { - } - } - - [Fact] - public void ConventionBasedStartup_Configure() - { - // Arrange - var instance = new MyStartup8(); - var startup = new ConventionBasedStartup(instance); - - var services = new ServiceCollection().AddSingleton("foo").BuildServiceProvider(); - var builder = new WebAssemblyBlazorApplicationBuilder(services); - - // Act - startup.Configure(builder, services); - - // Assert - Assert.Collection( - instance.Arguments, - a => Assert.Same(builder, a), - a => Assert.Equal("foo", a)); - } - - private class MyStartup8 - { - public List Arguments { get; } = new List(); - - public void Configure(IComponentsApplicationBuilder app, string foo) - { - Arguments.Add(app); - Arguments.Add(foo); - } - } - } -} diff --git a/src/Components/Blazor/Blazor/test/Hosting/RootComponentMappingTest.cs b/src/Components/Blazor/Blazor/test/Hosting/RootComponentMappingTest.cs new file mode 100644 index 0000000000..7249402880 --- /dev/null +++ b/src/Components/Blazor/Blazor/test/Hosting/RootComponentMappingTest.cs @@ -0,0 +1,37 @@ +// 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 System; +using System.Text; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Hosting +{ + public class RootComponentMappingTest + { + [Fact] + public void Constructor_ValidatesComponentType_Success() + { + // Arrange + // Act + var mapping = new RootComponentMapping(typeof(Router), "test"); + + // Assert (does not throw) + GC.KeepAlive(mapping); + } + + [Fact] + public void Constructor_ValidatesComponentType_Failure() + { + // Arrange + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new RootComponentMapping(typeof(StringBuilder), "test"), + "componentType", + $"The type '{nameof(StringBuilder)}' must implement IComponent to be used as a root component."); + } + } +} diff --git a/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostBuilderTest.cs b/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostBuilderTest.cs index 4acf6f99a3..77b6583d26 100644 --- a/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostBuilderTest.cs +++ b/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostBuilderTest.cs @@ -3,160 +3,100 @@ using System; using System.Collections.Generic; +using System.Net.Http; using System.Text; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.JSInterop; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Hosting.Test +namespace Microsoft.AspNetCore.Blazor.Hosting { public class WebAssemblyHostBuilderTest { [Fact] - public void HostBuilder_CanCallBuild_BuildsServices() + public void Build_AllowsConfiguringConfiguration() { // Arrange - var builder = new WebAssemblyHostBuilder(); + var builder = WebAssemblyHostBuilder.CreateDefault(); - // Act - var host = builder.Build(); - - // Assert - Assert.NotNull(host.Services.GetService(typeof(IWebAssemblyHost))); - } - - [Fact] - public void HostBuilder_CanConfigureAdditionalServices() - { - // Arrange - var builder = new WebAssemblyHostBuilder(); - builder.ConfigureServices((c, s) => s.AddSingleton("foo")); - builder.ConfigureServices((c, s) => s.AddSingleton(new StringBuilder("bar"))); - - // Act - var host = builder.Build(); - - // Assert - Assert.Equal("foo", host.Services.GetService(typeof(string))); - Assert.Equal("bar", host.Services.GetService(typeof(StringBuilder)).ToString()); - } - - [Fact] - public void HostBuilder_UseBlazorStartup_CanConfigureAdditionalServices() - { - // Arrange - var builder = new WebAssemblyHostBuilder(); - builder.UseBlazorStartup(); - builder.ConfigureServices((c, s) => s.AddSingleton(new StringBuilder("bar"))); - - // Act - var host = builder.Build(); - - // Assert - Assert.Equal("foo", host.Services.GetService(typeof(string))); - Assert.Equal("bar", host.Services.GetService(typeof(StringBuilder)).ToString()); - } - - [Fact] - public void HostBuilder_UseBlazorStartup_DoesNotAllowMultiple() - { - // Arrange - var builder = new WebAssemblyHostBuilder(); - builder.UseBlazorStartup(); - - // Act - var ex = Assert.Throws(() => builder.UseBlazorStartup()); - - // Assert - Assert.Equal("A startup class has already been registered.", ex.Message); - } - - private class MyStartup - { - public void ConfigureServices(IServiceCollection services) + builder.Configuration.AddInMemoryCollection(new[] { - services.AddSingleton("foo"); - } - } - - [Fact] - public void HostBuilder_CanCustomizeServiceFactory() - { - // Arrange - var builder = new WebAssemblyHostBuilder(); - builder.UseServiceProviderFactory(new TestServiceProviderFactory()); - - // Act - var host = builder.Build(); - - // Assert - Assert.IsType(host.Services); - } - - [Fact] - public void HostBuilder_CanCustomizeServiceFactoryWithContext() - { - // Arrange - var builder = new WebAssemblyHostBuilder(); - builder.UseServiceProviderFactory(context => - { - Assert.NotNull(context.Properties); - Assert.Same(builder.Properties, context.Properties); - return new TestServiceProviderFactory(); + new KeyValuePair("key", "value"), }); // Act var host = builder.Build(); // Assert - Assert.IsType(host.Services); + Assert.Equal("value", host.Configuration["key"]); } - private class TestServiceProvider : IServiceProvider + [Fact] + public void Build_AllowsConfiguringServices() { - private readonly IServiceProvider _underlyingProvider; + // Arrange + var builder = WebAssemblyHostBuilder.CreateDefault(); - public TestServiceProvider(IServiceProvider underlyingProvider) - { - _underlyingProvider = underlyingProvider; - } + // This test also verifies that we create a scope. + builder.Services.AddScoped(); - public object GetService(Type serviceType) + // Act + var host = builder.Build(); + + // Assert + Assert.NotNull(host.Services.GetRequiredService()); + } + + [Fact] + public void Build_AddsConfigurationToServices() + { + // Arrange + var builder = WebAssemblyHostBuilder.CreateDefault(); + + builder.Configuration.AddInMemoryCollection(new[] { - if (serviceType == typeof(IWebAssemblyHost)) + new KeyValuePair("key", "value"), + }); + + // Act + var host = builder.Build(); + + // Assert + var configuration = host.Services.GetRequiredService(); + Assert.Equal("value", configuration["key"]); + } + + private static IReadOnlyList DefaultServiceTypes + { + get + { + return new Type[] { - // Since the test will make assertions about the resulting IWebAssemblyHost, - // show that custom DI containers have the power to substitute themselves - // as the IServiceProvider - return new WebAssemblyHost( - this, _underlyingProvider.GetRequiredService()); - } - else - { - return _underlyingProvider.GetService(serviceType); - } + typeof(IJSRuntime), + typeof(NavigationManager), + typeof(INavigationInterception), + typeof(ILoggerFactory), + typeof(HttpClient), + typeof(ILogger<>), + }; } } - private class TestServiceProviderFactory : IServiceProviderFactory + [Fact] + public void Constructor_AddsDefaultServices() { - public IServiceCollection CreateBuilder(IServiceCollection services) - { - return new TestServiceCollection(services); - } + // Arrange & Act + var builder = WebAssemblyHostBuilder.CreateDefault(); - public IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection) + // Assert + Assert.Equal(DefaultServiceTypes.Count, builder.Services.Count); + foreach (var type in DefaultServiceTypes) { - Assert.IsType(serviceCollection); - return new TestServiceProvider(serviceCollection.BuildServiceProvider()); - } - - class TestServiceCollection : List, IServiceCollection - { - public TestServiceCollection(IEnumerable collection) - : base(collection) - { - } + Assert.Single(builder.Services, d => d.ServiceType == type); } } } diff --git a/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostTest.cs b/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostTest.cs index f99245e317..b838334566 100644 --- a/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostTest.cs +++ b/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostTest.cs @@ -2,64 +2,89 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.Builder; -using Microsoft.AspNetCore.Components.Hosting; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.JSInterop; -using Mono.WebAssembly.Interop; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Hosting.Test +namespace Microsoft.AspNetCore.Blazor.Hosting { public class WebAssemblyHostTest { - [Fact] - public async Task BrowserHost_StartAsync_ThrowsWithoutStartup() + // This won't happen in the product code, but we need to be able to safely call RunAsync + // to be able to test a few of the other details. + [Fact] + public async Task RunAsync_CanExitBasedOnCancellationToken() { // Arrange - var builder = new WebAssemblyHostBuilder(); + var builder = WebAssemblyHostBuilder.CreateDefault(); var host = builder.Build(); - // Act - var ex = await Assert.ThrowsAsync(async () => await host.StartAsync()); + var cts = new CancellationTokenSource(); - // Assert - Assert.Equal( - "Could not find a registered Blazor Startup class. " + - "Using IWebAssemblyHost requires a call to IWebAssemblyHostBuilder.UseBlazorStartup.", - ex.Message); + // Act + var task = host.RunAsyncCore(cts.Token); + + cts.Cancel(); + await task.TimeoutAfter(TimeSpan.FromSeconds(3)); + + // Assert (does not throw) } [Fact] - public async Task BrowserHost_StartAsync_RunsConfigureMethod() + public async Task RunAsync_CallingTwiceCausesException() { // Arrange - var builder = new WebAssemblyHostBuilder(); - - var startup = new MockStartup(); - builder.ConfigureServices((c, s) => { s.AddSingleton(startup); }); - + var builder = WebAssemblyHostBuilder.CreateDefault(); var host = builder.Build(); + var cts = new CancellationTokenSource(); + var task = host.RunAsyncCore(cts.Token); + // Act - await host.StartAsync(); + var ex = await Assert.ThrowsAsync(() => host.RunAsyncCore(cts.Token)); + + cts.Cancel(); + await task.TimeoutAfter(TimeSpan.FromSeconds(3)); // Assert - Assert.True(startup.ConfigureCalled); + Assert.Equal("The host has already started.", ex.Message); } - private class MockStartup : IBlazorStartup + [Fact] + public async Task DisposeAsync_CanDisposeAfterCallingRunAsync() { - public bool ConfigureCalled { get; set; } + // Arrange + var builder = WebAssemblyHostBuilder.CreateDefault(); + builder.Services.AddSingleton(); + var host = builder.Build(); - public void Configure(IComponentsApplicationBuilder app, IServiceProvider services) + var disposable = host.Services.GetRequiredService(); + + var cts = new CancellationTokenSource(); + + // Act + await using (host) { - ConfigureCalled = true; + var task = host.RunAsyncCore(cts.Token); + + cts.Cancel(); + await task.TimeoutAfter(TimeSpan.FromSeconds(3)); } - public void ConfigureServices(IServiceCollection services) + // Assert + Assert.Equal(1, disposable.DisposeCount); + } + + private class DisposableService : IAsyncDisposable + { + public int DisposeCount { get; private set; } + + public ValueTask DisposeAsync() { + DisposeCount++; + return new ValueTask(Task.CompletedTask); } } } diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Client/Program.cs b/src/Components/Blazor/testassets/HostedInAspNet.Client/Program.cs index 69ee439533..e922c2996f 100644 --- a/src/Components/Blazor/testassets/HostedInAspNet.Client/Program.cs +++ b/src/Components/Blazor/testassets/HostedInAspNet.Client/Program.cs @@ -1,19 +1,19 @@ // 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 System.Threading.Tasks; using Microsoft.AspNetCore.Blazor.Hosting; namespace HostedInAspNet.Client { public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { - CreateHostBuilder(args).Build().Run(); - } + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("app"); - public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => - BlazorWebAssemblyHost.CreateDefaultBuilder() - .UseBlazorStartup(); + await builder.Build().RunAsync(); + } } } diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Client/Startup.cs b/src/Components/Blazor/testassets/HostedInAspNet.Client/Startup.cs deleted file mode 100644 index a06163b9e0..0000000000 --- a/src/Components/Blazor/testassets/HostedInAspNet.Client/Startup.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Components.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace HostedInAspNet.Client -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - } - - public void Configure(IComponentsApplicationBuilder app) - { - app.AddComponent("app"); - } - } -} diff --git a/src/Components/Blazor/testassets/StandaloneApp/Program.cs b/src/Components/Blazor/testassets/StandaloneApp/Program.cs index 530de72870..8da14834b6 100644 --- a/src/Components/Blazor/testassets/StandaloneApp/Program.cs +++ b/src/Components/Blazor/testassets/StandaloneApp/Program.cs @@ -1,19 +1,19 @@ // 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 System.Threading.Tasks; using Microsoft.AspNetCore.Blazor.Hosting; namespace StandaloneApp { public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { - CreateHostBuilder(args).Build().Run(); - } + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("app"); - public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => - BlazorWebAssemblyHost.CreateDefaultBuilder() - .UseBlazorStartup(); + await builder.Build().RunAsync(); + } } } diff --git a/src/Components/Blazor/testassets/StandaloneApp/Startup.cs b/src/Components/Blazor/testassets/StandaloneApp/Startup.cs deleted file mode 100644 index 79564e8d9d..0000000000 --- a/src/Components/Blazor/testassets/StandaloneApp/Startup.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Components.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace StandaloneApp -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - } - - public void Configure(IComponentsApplicationBuilder app) - { - app.AddComponent("app"); - } - } -} diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs index 0d74c8c6b7..6a721adf63 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs @@ -100,7 +100,7 @@ namespace Wasm.Performance.Driver Format = "n2", }); - var testAssembly = typeof(TestApp.Startup).Assembly; + var testAssembly = typeof(TestApp.Program).Assembly; var testAssemblyLocation = new FileInfo(testAssembly.Location); var testApp = new DirectoryInfo(Path.Combine( testAssemblyLocation.Directory.FullName, @@ -143,7 +143,7 @@ namespace Wasm.Performance.Driver var args = new[] { "--urls", "http://127.0.0.1:0", - "--applicationpath", typeof(TestApp.Startup).Assembly.Location, + "--applicationpath", typeof(TestApp.Program).Assembly.Location, }; var host = DevHostServerProgram.BuildWebHost(args); diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs index 403bc37c9c..57a3697382 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs @@ -1,19 +1,19 @@ // 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 System.Threading.Tasks; using Microsoft.AspNetCore.Blazor.Hosting; namespace Wasm.Performance.TestApp { public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { - CreateHostBuilder(args).Build().Run(); - } + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("app"); - public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => - BlazorWebAssemblyHost.CreateDefaultBuilder() - .UseBlazorStartup(); + await builder.Build().RunAsync(); + } } } diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Startup.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Startup.cs deleted file mode 100644 index c79b0efb8c..0000000000 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Startup.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Components.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace Wasm.Performance.TestApp -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - } - - public void Configure(IComponentsApplicationBuilder app) - { - app.AddComponent("app"); - } - } -} diff --git a/src/Components/test/testassets/BasicTestApp/Program.cs b/src/Components/test/testassets/BasicTestApp/Program.cs index 2be7d81b4e..0c62f05bd1 100644 --- a/src/Components/test/testassets/BasicTestApp/Program.cs +++ b/src/Components/test/testassets/BasicTestApp/Program.cs @@ -3,8 +3,13 @@ using System; using System.Globalization; +using System.Runtime.InteropServices; using System.Threading.Tasks; +using BasicTestApp.AuthTest; using Microsoft.AspNetCore.Blazor.Hosting; +using Microsoft.AspNetCore.Blazor.Http; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.DependencyInjection; using Mono.WebAssembly.Interop; namespace BasicTestApp @@ -18,12 +23,26 @@ namespace BasicTestApp // We want the culture to be en-US so that the tests for bind can work consistently. CultureInfo.CurrentCulture = new CultureInfo("en-US"); - CreateHostBuilder(args).Build().Run(); - } + var builder = WebAssemblyHostBuilder.CreateDefault(args); - public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => - BlazorWebAssemblyHost.CreateDefaultBuilder() - .UseBlazorStartup(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"))) + { + // Needed because the test server runs on a different port than the client app, + // and we want to test sending/receiving cookies under this config + WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include; + } + + builder.RootComponents.Add("root"); + + builder.Services.AddSingleton(); + builder.Services.AddAuthorizationCore(options => + { + options.AddPolicy("NameMustStartWithB", policy => + policy.RequireAssertion(ctx => ctx.User.Identity.Name?.StartsWith("B") ?? false)); + }); + + await builder.Build().RunAsync(); + } // Supports E2E tests in StartupErrorNotificationTest private static async Task SimulateErrorsIfNeededForTest() diff --git a/src/Components/test/testassets/BasicTestApp/Startup.cs b/src/Components/test/testassets/BasicTestApp/Startup.cs deleted file mode 100644 index 008a988316..0000000000 --- a/src/Components/test/testassets/BasicTestApp/Startup.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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 System.Runtime.InteropServices; -using BasicTestApp.AuthTest; -using Microsoft.AspNetCore.Blazor.Http; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace BasicTestApp -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - - services.AddAuthorizationCore(options => - { - options.AddPolicy("NameMustStartWithB", policy => - policy.RequireAssertion(ctx => ctx.User.Identity.Name?.StartsWith("B") ?? false)); - }); - } - - public void Configure(IComponentsApplicationBuilder app) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"))) - { - // Needed because the test server runs on a different port than the client app, - // and we want to test sending/receiving cookies underling this config - WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include; - } - - app.AddComponent("root"); - } - } -} diff --git a/src/Components/test/testassets/TestServer/CorsStartup.cs b/src/Components/test/testassets/TestServer/CorsStartup.cs index a4d91a84aa..28dda32d90 100644 --- a/src/Components/test/testassets/TestServer/CorsStartup.cs +++ b/src/Components/test/testassets/TestServer/CorsStartup.cs @@ -46,7 +46,7 @@ namespace TestServer app.Map("/subdir", app => { app.UseStaticFiles(); - app.UseClientSideBlazorFiles(); + app.UseClientSideBlazorFiles(); app.UseRouting(); @@ -55,7 +55,7 @@ namespace TestServer app.UseEndpoints(endpoints => { endpoints.MapControllers(); - endpoints.MapFallbackToClientSideBlazor("index.html"); + endpoints.MapFallbackToClientSideBlazor("index.html"); }); }); } diff --git a/src/Components/test/testassets/TestServer/InternationalizationStartup.cs b/src/Components/test/testassets/TestServer/InternationalizationStartup.cs index d508ed797b..7521ebe34b 100644 --- a/src/Components/test/testassets/TestServer/InternationalizationStartup.cs +++ b/src/Components/test/testassets/TestServer/InternationalizationStartup.cs @@ -37,7 +37,7 @@ namespace TestServer app.Map("/subdir", app => { app.UseStaticFiles(); - app.UseClientSideBlazorFiles(); + app.UseClientSideBlazorFiles(); app.UseRequestLocalization(options => { diff --git a/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs b/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs index 483dd00d78..d32f48f8e3 100644 --- a/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs +++ b/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs @@ -36,7 +36,7 @@ namespace TestServer // The client-side files middleware needs to be here because the base href in hardcoded to /subdir/ app.Map("/subdir", app => { - app.UseClientSideBlazorFiles(); + app.UseClientSideBlazorFiles(); }); // The calls to `Map` allow us to test each of these overloads, while keeping them isolated. @@ -46,7 +46,7 @@ namespace TestServer app.UseEndpoints(endpoints => { - endpoints.MapFallbackToClientSideBlazor("index.html"); + endpoints.MapFallbackToClientSideBlazor("index.html"); }); }); @@ -56,7 +56,7 @@ namespace TestServer app.UseEndpoints(endpoints => { - endpoints.MapFallbackToClientSideBlazor("test/{*path:nonfile}", "index.html"); + endpoints.MapFallbackToClientSideBlazor("test/{*path:nonfile}", "index.html"); }); }); @@ -66,7 +66,7 @@ namespace TestServer app.UseEndpoints(endpoints => { - endpoints.MapFallbackToClientSideBlazor(typeof(BasicTestApp.Startup).Assembly.Location, "index.html"); + endpoints.MapFallbackToClientSideBlazor(typeof(BasicTestApp.Program).Assembly.Location, "index.html"); }); }); @@ -76,7 +76,7 @@ namespace TestServer app.UseEndpoints(endpoints => { - endpoints.MapFallbackToClientSideBlazor(typeof(BasicTestApp.Startup).Assembly.Location, "test/{*path:nonfile}", "index.html"); + endpoints.MapFallbackToClientSideBlazor(typeof(BasicTestApp.Program).Assembly.Location, "test/{*path:nonfile}", "index.html"); }); }); } diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Program.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Program.cs index 03d510452d..06790b9c57 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Program.cs +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Program.cs @@ -1,4 +1,12 @@ -using Microsoft.AspNetCore.Blazor.Hosting; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Text; +using Microsoft.AspNetCore.Blazor.Hosting; +using Microsoft.Extensions.DependencyInjection; +#if (!NoAuth) +using Microsoft.AspNetCore.Components.Authorization; +#endif #if (Hosted) namespace BlazorWasm_CSharp.Client @@ -8,13 +16,19 @@ namespace BlazorWasm_CSharp { public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { - CreateHostBuilder(args).Build().Run(); - } + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("app"); - public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => - BlazorWebAssemblyHost.CreateDefaultBuilder() - .UseBlazorStartup(); + // use builder.Services to configure application services. +#if (IndividualLocalAuth) + builder.Services.AddOptions(); + builder.Services.AddAuthorizationCore(); + builder.Services.AddSingleton(); +#endif + + await builder.Build().RunAsync(); + } } } diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Startup.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Startup.cs deleted file mode 100644 index 38fd10a30e..0000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Startup.cs +++ /dev/null @@ -1,28 +0,0 @@ -#if (!NoAuth) -using Microsoft.AspNetCore.Components.Authorization; -#endif -using Microsoft.AspNetCore.Components.Builder; -using Microsoft.Extensions.DependencyInjection; - -#if (Hosted) -namespace BlazorWasm_CSharp.Client -#else -namespace BlazorWasm_CSharp -#endif -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { -#if (IndividualLocalAuth) - services.AddAuthorizationCore(); - services.AddSingleton(); -#endif - } - - public void Configure(IComponentsApplicationBuilder app) - { - app.AddComponent("app"); - } - } -} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs index fd1767bd47..eb96bae0e8 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs @@ -83,7 +83,7 @@ namespace BlazorWasm_CSharp.Server #endif app.UseStaticFiles(); - app.UseClientSideBlazorFiles(); + app.UseClientSideBlazorFiles(); app.UseRouting(); @@ -99,7 +99,7 @@ namespace BlazorWasm_CSharp.Server #endif endpoints.MapControllers(); - endpoints.MapFallbackToClientSideBlazor("index.html"); + endpoints.MapFallbackToClientSideBlazor("index.html"); }); } }