From d30c407dd0a8e3fb4beeb1ab9c226be24bbdbce4 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 14 Dec 2018 17:37:14 +0000 Subject: [PATCH] Support IServiceProviderFactory in WebAssemblyHostBuilder (imported from Blazor PR 1623) (#4785) --- .../Hosting/IWebAssemblyHostBuilder.cs | 12 +++ .../IWebAssemblyServiceFactoryAdapter.cs | 17 ++++ .../Hosting/WebAssemblyHostBuilder.cs | 24 +++++- .../WebAssemblyServiceFactoryAdapter.cs | 52 ++++++++++++ .../Hosting/WebAssemblyHostBuilderTest.cs | 83 +++++++++++++++++++ 5 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/IWebAssemblyServiceFactoryAdapter.cs create mode 100644 src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/WebAssemblyServiceFactoryAdapter.cs diff --git a/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/IWebAssemblyHostBuilder.cs b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/IWebAssemblyHostBuilder.cs index 6ede4fa6b6..d67af37071 100644 --- a/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/IWebAssemblyHostBuilder.cs +++ b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/IWebAssemblyHostBuilder.cs @@ -17,6 +17,18 @@ namespace Microsoft.AspNetCore.Components.Hosting /// 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. /// diff --git a/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/IWebAssemblyServiceFactoryAdapter.cs b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/IWebAssemblyServiceFactoryAdapter.cs new file mode 100644 index 0000000000..f579a334bc --- /dev/null +++ b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/IWebAssemblyServiceFactoryAdapter.cs @@ -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. + +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Components.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/src/Microsoft.AspNetCore.Components.Browser/Hosting/WebAssemblyHostBuilder.cs b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/WebAssemblyHostBuilder.cs index 4e63b86440..4c56cf67ec 100644 --- a/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/WebAssemblyHostBuilder.cs +++ b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/WebAssemblyHostBuilder.cs @@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Components.Hosting private List> _configureServicesActions = new List>(); private bool _hostBuilt; private WebAssemblyHostBuilderContext _BrowserHostBuilderContext; + private IWebAssemblyServiceFactoryAdapter _serviceProviderFactory = new WebAssemblyServiceFactoryAdapter(new DefaultServiceProviderFactory()); private IServiceProvider _appServices; /// @@ -39,6 +40,26 @@ namespace Microsoft.AspNetCore.Components.Hosting return this; } + /// + /// Overrides the factory used to create the service provider. + /// + /// The same instance of the for chaining. + public IWebAssemblyHostBuilder UseServiceProviderFactory(IServiceProviderFactory factory) + { + _serviceProviderFactory = new WebAssemblyServiceFactoryAdapter(factory ?? throw new ArgumentNullException(nameof(factory))); + return this; + } + + /// + /// Overrides the factory used to create the service provider. + /// + /// The same instance of the for chaining. + public IWebAssemblyHostBuilder UseServiceProviderFactory(Func> factory) + { + _serviceProviderFactory = new WebAssemblyServiceFactoryAdapter(() => _BrowserHostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory))); + return this; + } + /// /// Run the given actions to initialize the host. This can only be called once. /// @@ -85,7 +106,8 @@ namespace Microsoft.AspNetCore.Components.Hosting configureServicesAction(_BrowserHostBuilderContext, services); } - _appServices = services.BuildServiceProvider(); + var builder = _serviceProviderFactory.CreateBuilder(services); + _appServices = _serviceProviderFactory.CreateServiceProvider(builder); } } } \ No newline at end of file diff --git a/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/WebAssemblyServiceFactoryAdapter.cs b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/WebAssemblyServiceFactoryAdapter.cs new file mode 100644 index 0000000000..d184141f87 --- /dev/null +++ b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Hosting/WebAssemblyServiceFactoryAdapter.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Components.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/test/Microsoft.AspNetCore.Components.Browser.Test/Hosting/WebAssemblyHostBuilderTest.cs b/src/Components/test/Microsoft.AspNetCore.Components.Browser.Test/Hosting/WebAssemblyHostBuilderTest.cs index 63b5745326..fe01143ac6 100644 --- a/src/Components/test/Microsoft.AspNetCore.Components.Browser.Test/Hosting/WebAssemblyHostBuilderTest.cs +++ b/src/Components/test/Microsoft.AspNetCore.Components.Browser.Test/Hosting/WebAssemblyHostBuilderTest.cs @@ -2,8 +2,10 @@ // 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.Text; using Microsoft.Extensions.DependencyInjection; +using Microsoft.JSInterop; using Xunit; namespace Microsoft.AspNetCore.Components.Hosting @@ -76,5 +78,86 @@ namespace Microsoft.AspNetCore.Components.Hosting 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(); + }); + + // Act + var host = builder.Build(); + + // Assert + Assert.IsType(host.Services); + } + + private class TestServiceProvider : IServiceProvider + { + private readonly IServiceProvider _underlyingProvider; + + public TestServiceProvider(IServiceProvider underlyingProvider) + { + _underlyingProvider = underlyingProvider; + } + + public object GetService(Type serviceType) + { + if (serviceType == typeof(IWebAssemblyHost)) + { + // 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); + } + } + } + + private class TestServiceProviderFactory : IServiceProviderFactory + { + public IServiceCollection CreateBuilder(IServiceCollection services) + { + return new TestServiceCollection(services); + } + + public IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection) + { + Assert.IsType(serviceCollection); + return new TestServiceProvider(serviceCollection.BuildServiceProvider()); + } + + class TestServiceCollection : List, IServiceCollection + { + public TestServiceCollection(IEnumerable collection) + : base(collection) + { + } + } + } } }