diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj index dd423bb0f9769..179706a88526f 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0;netstandard2.0 + netcoreapp3.0;netstandard2.0;netstandard2.1 @@ -10,5 +10,10 @@ + + + + + diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.netstandard2.0.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.netstandard2.0.cs index 0388261d82126..7825333532665 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.netstandard2.0.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.netstandard2.0.cs @@ -33,10 +33,11 @@ public static partial class ServiceCollectionContainerBuilderExtensions public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions options) { throw null; } public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, bool validateScopes) { throw null; } } - public sealed partial class ServiceProvider : System.IDisposable, System.IServiceProvider + public sealed partial class ServiceProvider : System.IAsyncDisposable, System.IDisposable, System.IServiceProvider { internal ServiceProvider() { } public void Dispose() { } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } public object GetService(System.Type serviceType) { throw null; } } public partial class ServiceProviderOptions diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.netstandard2.1.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.netstandard2.1.cs new file mode 100644 index 0000000000000..7825333532665 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.netstandard2.1.cs @@ -0,0 +1,49 @@ +// 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.Extensions.DependencyInjection +{ + public partial class DefaultServiceProviderFactory : Microsoft.Extensions.DependencyInjection.IServiceProviderFactory + { + public DefaultServiceProviderFactory() { } + public DefaultServiceProviderFactory(Microsoft.Extensions.DependencyInjection.ServiceProviderOptions options) { } + public Microsoft.Extensions.DependencyInjection.IServiceCollection CreateBuilder(Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public System.IServiceProvider CreateServiceProvider(Microsoft.Extensions.DependencyInjection.IServiceCollection containerBuilder) { throw null; } + } + public partial class ServiceCollection : Microsoft.Extensions.DependencyInjection.IServiceCollection, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.IEnumerable + { + public ServiceCollection() { } + public int Count { get { throw null; } } + public bool IsReadOnly { get { throw null; } } + public Microsoft.Extensions.DependencyInjection.ServiceDescriptor this[int index] { get { throw null; } set { } } + public void Clear() { } + public bool Contains(Microsoft.Extensions.DependencyInjection.ServiceDescriptor item) { throw null; } + public void CopyTo(Microsoft.Extensions.DependencyInjection.ServiceDescriptor[] array, int arrayIndex) { } + public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } + public int IndexOf(Microsoft.Extensions.DependencyInjection.ServiceDescriptor item) { throw null; } + public void Insert(int index, Microsoft.Extensions.DependencyInjection.ServiceDescriptor item) { } + public bool Remove(Microsoft.Extensions.DependencyInjection.ServiceDescriptor item) { throw null; } + public void RemoveAt(int index) { } + void System.Collections.Generic.ICollection.Add(Microsoft.Extensions.DependencyInjection.ServiceDescriptor item) { } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } + public static partial class ServiceCollectionContainerBuilderExtensions + { + public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions options) { throw null; } + public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, bool validateScopes) { throw null; } + } + public sealed partial class ServiceProvider : System.IAsyncDisposable, System.IDisposable, System.IServiceProvider + { + internal ServiceProvider() { } + public void Dispose() { } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + public object GetService(System.Type serviceType) { throw null; } + } + public partial class ServiceProviderOptions + { + public ServiceProviderOptions() { } + public bool ValidateOnBuild { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool ValidateScopes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj index 583c76098792a..c87d8f9519f8d 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj @@ -2,7 +2,7 @@ Default implementation of dependency injection for Microsoft.Extensions.DependencyInjection. - netcoreapp3.0;net461;netstandard2.0 + netcoreapp3.0;net461;netstandard2.0;netstandard2.1 true dependencyinjection;di true @@ -10,7 +10,6 @@ True $(DefineConstants);IL_EMIT - $(DefineConstants);DISPOSE_ASYNC False @@ -26,6 +25,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IServiceProviderEngine.cs index dcb9c2d810a54..6a89fd940b452 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IServiceProviderEngine.cs @@ -5,10 +5,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal interface IServiceProviderEngine : IServiceProvider, IDisposable -#if DISPOSE_ASYNC - , IAsyncDisposable -#endif + internal interface IServiceProviderEngine : IServiceProvider, IDisposable, IAsyncDisposable { IServiceScope RootScope { get; } void ValidateService(ServiceDescriptor descriptor); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceCallSite.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceCallSite.cs index be33907ef2e09..3cd890f897941 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceCallSite.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceCallSite.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Reflection; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { @@ -21,17 +20,9 @@ protected ServiceCallSite(ResultCache cache) public abstract CallSiteKind Kind { get; } public ResultCache Cache { get; } - public bool CaptureDisposable - { - get - { - return ImplementationType == null || - typeof(IDisposable).GetTypeInfo().IsAssignableFrom(ImplementationType.GetTypeInfo()) -#if DISPOSE_ASYNC - || typeof(IAsyncDisposable).GetTypeInfo().IsAssignableFrom(ImplementationType.GetTypeInfo()) -#endif - ; - } - } + public bool CaptureDisposable => + ImplementationType == null || + typeof(IDisposable).IsAssignableFrom(ImplementationType) || + typeof(IAsyncDisposable).IsAssignableFrom(ImplementationType); } } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs index 6c1254f236259..763531aec3e5b 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs @@ -69,13 +69,11 @@ public void Dispose() Root.Dispose(); } -#if DISPOSE_ASYNC public ValueTask DisposeAsync() { _disposed = true; return Root.DisposeAsync(); } -#endif internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) { diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs index 024858af522c0..07ee8843c6045 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs @@ -9,10 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { - internal class ServiceProviderEngineScope : IServiceScope, IServiceProvider -#if DISPOSE_ASYNC - , IAsyncDisposable -#endif + internal class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IAsyncDisposable { // For testing only internal Action _captureDisposableCallback; @@ -48,12 +45,7 @@ internal object CaptureDisposable(object service) _captureDisposableCallback?.Invoke(service); - if (ReferenceEquals(this, service) || - !(service is IDisposable -#if DISPOSE_ASYNC - || service is IAsyncDisposable -#endif - )) + if (ReferenceEquals(this, service) || !(service is IDisposable || service is IAsyncDisposable)) { return service; } @@ -90,7 +82,6 @@ public void Dispose() } } -#if DISPOSE_ASYNC public ValueTask DisposeAsync() { var toDispose = BeginDispose(); @@ -146,7 +137,6 @@ async ValueTask Await(int i, ValueTask vt) } } } -#endif private List BeginDispose() { diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs index e3d5bd709f3b4..b95663b5fa246 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs @@ -12,10 +12,7 @@ namespace Microsoft.Extensions.DependencyInjection /// /// The default IServiceProvider. /// - public sealed class ServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback -#if DISPOSE_ASYNC - , IAsyncDisposable -#endif + public sealed class ServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback, IAsyncDisposable { private readonly IServiceProviderEngine _engine; @@ -111,12 +108,10 @@ void IServiceProviderEngineCallback.OnResolve(Type serviceType, IServiceScope sc _callSiteValidator.ValidateResolution(serviceType, scope, _engine.RootScope); } -#if DISPOSE_ASYNC /// public ValueTask DisposeAsync() { return _engine.DisposeAsync(); } -#endif } } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/ServiceProviderContainerTests.AsyncDisposable.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/ServiceProviderContainerTests.AsyncDisposable.cs deleted file mode 100644 index 6260011d6c500..0000000000000 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/ServiceProviderContainerTests.AsyncDisposable.cs +++ /dev/null @@ -1,161 +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.Tasks; -using Microsoft.Extensions.DependencyInjection.Specification.Fakes; -using Xunit; - -namespace Microsoft.Extensions.DependencyInjection.Tests -{ -#if NETCOREAPP - public abstract partial class ServiceProviderContainerTests - { - [Fact] - public async Task ProviderDisposeAsyncCallsDisposeAsyncOnServices() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddTransient(); - - var serviceProvider = CreateServiceProvider(serviceCollection); - var disposable = serviceProvider.GetService(); - - await (serviceProvider as IAsyncDisposable).DisposeAsync(); - - Assert.True(disposable.DisposeAsyncCalled); - } - - [Fact] - public async Task ProviderDisposeAsyncPrefersDisposeAsyncOnServices() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddTransient(); - - var serviceProvider = CreateServiceProvider(serviceCollection); - var disposable = serviceProvider.GetService(); - - await (serviceProvider as IAsyncDisposable).DisposeAsync(); - - Assert.True(disposable.DisposeAsyncCalled); - } - - [Fact] - public void ProviderDisposePrefersServiceDispose() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddTransient(); - - var serviceProvider = CreateServiceProvider(serviceCollection); - var disposable = serviceProvider.GetService(); - - (serviceProvider as IDisposable).Dispose(); - - Assert.True(disposable.DisposeCalled); - } - - [Fact] - public void ProviderDisposeThrowsWhenOnlyDisposeAsyncImplemented() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddTransient(); - - var serviceProvider = CreateServiceProvider(serviceCollection); - var disposable = serviceProvider.GetService(); - - var exception = Assert.Throws(() => (serviceProvider as IDisposable).Dispose()); - Assert.Equal( - "'Microsoft.Extensions.DependencyInjection.Tests.ServiceProviderContainerTests+AsyncDisposable' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.", - exception.Message); - } - - [Fact] - public async Task ProviderScopeDisposeAsyncCallsDisposeAsyncOnServices() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddTransient(); - - var serviceProvider = CreateServiceProvider(serviceCollection); - var scope = serviceProvider.CreateScope(); - var disposable = scope.ServiceProvider.GetService(); - - await (scope as IAsyncDisposable).DisposeAsync(); - - Assert.True(disposable.DisposeAsyncCalled); - } - - [Fact] - public async Task ProviderScopeDisposeAsyncPrefersDisposeAsyncOnServices() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddTransient(); - - var serviceProvider = CreateServiceProvider(serviceCollection); - var scope = serviceProvider.CreateScope(); - var disposable = scope.ServiceProvider.GetService(); - - await (scope as IAsyncDisposable).DisposeAsync(); - - Assert.True(disposable.DisposeAsyncCalled); - } - - [Fact] - public void ProviderScopeDisposePrefersServiceDispose() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddTransient(); - - var serviceProvider = CreateServiceProvider(serviceCollection); - var scope = serviceProvider.CreateScope(); - var disposable = scope.ServiceProvider.GetService(); - - (scope as IDisposable).Dispose(); - - Assert.True(disposable.DisposeCalled); - } - - [Fact] - public void ProviderScopeDisposeThrowsWhenOnlyDisposeAsyncImplemented() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddTransient(); - - var serviceProvider = CreateServiceProvider(serviceCollection); - var scope = serviceProvider.CreateScope(); - var disposable = scope.ServiceProvider.GetService(); - - var exception = Assert.Throws(() => (scope as IDisposable).Dispose()); - Assert.Equal( - "'Microsoft.Extensions.DependencyInjection.Tests.ServiceProviderContainerTests+AsyncDisposable' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.", - exception.Message); - } - - private class AsyncDisposable: IFakeService, IAsyncDisposable - { - public bool DisposeAsyncCalled { get; private set; } - - public ValueTask DisposeAsync() - { - DisposeAsyncCalled = true; - return new ValueTask(Task.CompletedTask); - } - } - - private class SyncAsyncDisposable: IFakeService, IAsyncDisposable, IDisposable - { - public bool DisposeCalled { get; private set; } - public bool DisposeAsyncCalled { get; private set; } - - public void Dispose() - { - DisposeCalled = true; - } - - public ValueTask DisposeAsync() - { - DisposeAsyncCalled = true; - return new ValueTask(Task.CompletedTask); - } - } - } -#endif -} diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/ServiceProviderContainerTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/ServiceProviderContainerTests.cs index 47dfe659a3f0a..9d0fe3c1b7db6 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/ServiceProviderContainerTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/ServiceProviderContainerTests.cs @@ -9,12 +9,11 @@ using Microsoft.Extensions.DependencyInjection.Specification; using Microsoft.Extensions.DependencyInjection.Specification.Fakes; using Microsoft.Extensions.DependencyInjection.Tests.Fakes; -using Moq; using Xunit; namespace Microsoft.Extensions.DependencyInjection.Tests { - public abstract partial class ServiceProviderContainerTests : DependencyInjectionSpecificationTests + public abstract class ServiceProviderContainerTests : DependencyInjectionSpecificationTests { [Fact] public void RethrowOriginalExceptionFromConstructor() @@ -228,6 +227,125 @@ public void GenericIEnumerableItemCachedInTheRightSlot() Assert.Same(serviceRef1, servicesRef1); } + + [Fact] + public async Task ProviderDisposeAsyncCallsDisposeAsyncOnServices() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + + var serviceProvider = CreateServiceProvider(serviceCollection); + var disposable = serviceProvider.GetService(); + + await (serviceProvider as IAsyncDisposable).DisposeAsync(); + + Assert.True(disposable.DisposeAsyncCalled); + } + + [Fact] + public async Task ProviderDisposeAsyncPrefersDisposeAsyncOnServices() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + + var serviceProvider = CreateServiceProvider(serviceCollection); + var disposable = serviceProvider.GetService(); + + await (serviceProvider as IAsyncDisposable).DisposeAsync(); + + Assert.True(disposable.DisposeAsyncCalled); + } + + [Fact] + public void ProviderDisposePrefersServiceDispose() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + + var serviceProvider = CreateServiceProvider(serviceCollection); + var disposable = serviceProvider.GetService(); + + (serviceProvider as IDisposable).Dispose(); + + Assert.True(disposable.DisposeCalled); + } + + [Fact] + public void ProviderDisposeThrowsWhenOnlyDisposeAsyncImplemented() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + + var serviceProvider = CreateServiceProvider(serviceCollection); + var disposable = serviceProvider.GetService(); + + var exception = Assert.Throws(() => (serviceProvider as IDisposable).Dispose()); + Assert.Equal( + "'Microsoft.Extensions.DependencyInjection.Tests.ServiceProviderContainerTests+AsyncDisposable' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.", + exception.Message); + } + + [Fact] + public async Task ProviderScopeDisposeAsyncCallsDisposeAsyncOnServices() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + + var serviceProvider = CreateServiceProvider(serviceCollection); + var scope = serviceProvider.CreateScope(); + var disposable = scope.ServiceProvider.GetService(); + + await (scope as IAsyncDisposable).DisposeAsync(); + + Assert.True(disposable.DisposeAsyncCalled); + } + + [Fact] + public async Task ProviderScopeDisposeAsyncPrefersDisposeAsyncOnServices() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + + var serviceProvider = CreateServiceProvider(serviceCollection); + var scope = serviceProvider.CreateScope(); + var disposable = scope.ServiceProvider.GetService(); + + await (scope as IAsyncDisposable).DisposeAsync(); + + Assert.True(disposable.DisposeAsyncCalled); + } + + [Fact] + public void ProviderScopeDisposePrefersServiceDispose() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + + var serviceProvider = CreateServiceProvider(serviceCollection); + var scope = serviceProvider.CreateScope(); + var disposable = scope.ServiceProvider.GetService(); + + (scope as IDisposable).Dispose(); + + Assert.True(disposable.DisposeCalled); + } + + [Fact] + public void ProviderScopeDisposeThrowsWhenOnlyDisposeAsyncImplemented() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + + var serviceProvider = CreateServiceProvider(serviceCollection); + var scope = serviceProvider.CreateScope(); + var disposable = scope.ServiceProvider.GetService(); + + var exception = Assert.Throws(() => (scope as IDisposable).Dispose()); + Assert.Equal( + "'Microsoft.Extensions.DependencyInjection.Tests.ServiceProviderContainerTests+AsyncDisposable' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.", + exception.Message); + } + private class FakeMultipleServiceWithIEnumerableDependency: IFakeMultipleService { public FakeMultipleServiceWithIEnumerableDependency(IEnumerable fakeServices) @@ -249,5 +367,33 @@ public void Dispose() Disposed = true; } } + + private class AsyncDisposable : IFakeService, IAsyncDisposable + { + public bool DisposeAsyncCalled { get; private set; } + + public ValueTask DisposeAsync() + { + DisposeAsyncCalled = true; + return new ValueTask(Task.CompletedTask); + } + } + + private class SyncAsyncDisposable : IFakeService, IAsyncDisposable, IDisposable + { + public bool DisposeCalled { get; private set; } + public bool DisposeAsyncCalled { get; private set; } + + public void Dispose() + { + DisposeCalled = true; + } + + public ValueTask DisposeAsync() + { + DisposeAsyncCalled = true; + return new ValueTask(Task.CompletedTask); + } + } } } diff --git a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/ref/Microsoft.Extensions.Hosting.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/ref/Microsoft.Extensions.Hosting.Abstractions.csproj index 1a5603abf6ddb..d503f490e5c3e 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/ref/Microsoft.Extensions.Hosting.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/ref/Microsoft.Extensions.Hosting.Abstractions.csproj @@ -9,6 +9,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs index 4747ed47f7459..e09571aab99a1 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs @@ -65,13 +65,11 @@ public static async Task RunAsync(this IHost host, CancellationToken token = def } finally { -#if DISPOSE_ASYNC if (host is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else -#endif { host.Dispose(); } diff --git a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/Microsoft.Extensions.Hosting.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/Microsoft.Extensions.Hosting.Abstractions.csproj index 9f92368002d91..05928d254f770 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/Microsoft.Extensions.Hosting.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/Microsoft.Extensions.Hosting.Abstractions.csproj @@ -1,4 +1,4 @@ - + .NET Core hosting and startup abstractions for applications. @@ -8,7 +8,6 @@ true hosting Microsoft.Extensions.Hosting - $(DefineConstants);DISPOSE_ASYNC true true @@ -20,4 +19,8 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.csproj b/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.csproj index 874eb9b35cbe2..bb98b6a93d2a6 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.csproj @@ -1,7 +1,7 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netstandard2.1;netcoreapp3.0 @@ -17,6 +17,22 @@ + + + + + + + + + + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.netstandard2.1.cs b/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.netstandard2.1.cs new file mode 100644 index 0000000000000..a35619b823692 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.netstandard2.1.cs @@ -0,0 +1,78 @@ +// 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.Extensions.Hosting +{ + public partial class ConsoleLifetimeOptions + { + public ConsoleLifetimeOptions() { } + public bool SuppressStatusMessages { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public static partial class Host + { + public static Microsoft.Extensions.Hosting.IHostBuilder CreateDefaultBuilder() { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder CreateDefaultBuilder(string[] args) { throw null; } + } + public partial class HostBuilder : Microsoft.Extensions.Hosting.IHostBuilder + { + public HostBuilder() { } + public System.Collections.Generic.IDictionary Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Hosting.IHost Build() { throw null; } + public Microsoft.Extensions.Hosting.IHostBuilder ConfigureAppConfiguration(System.Action configureDelegate) { throw null; } + public Microsoft.Extensions.Hosting.IHostBuilder ConfigureContainer(System.Action configureDelegate) { throw null; } + public Microsoft.Extensions.Hosting.IHostBuilder ConfigureHostConfiguration(System.Action configureDelegate) { throw null; } + public Microsoft.Extensions.Hosting.IHostBuilder ConfigureServices(System.Action configureDelegate) { throw null; } + public Microsoft.Extensions.Hosting.IHostBuilder UseServiceProviderFactory(Microsoft.Extensions.DependencyInjection.IServiceProviderFactory factory) { throw null; } + public Microsoft.Extensions.Hosting.IHostBuilder UseServiceProviderFactory(System.Func> factory) { throw null; } + } + public static partial class HostingHostBuilderExtensions + { + public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureAppConfiguration(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureContainer(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureLogging(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureLogging) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureLogging(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureLogging) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureServices(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } + public static System.Threading.Tasks.Task RunConsoleAsync(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureOptions, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.Task RunConsoleAsync(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder UseConsoleLifetime(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder UseConsoleLifetime(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configureOptions) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder UseContentRoot(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, string contentRoot) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder UseDefaultServiceProvider(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configure) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder UseDefaultServiceProvider(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action configure) { throw null; } + public static Microsoft.Extensions.Hosting.IHostBuilder UseEnvironment(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, string environment) { throw null; } + } + public partial class HostOptions + { + public HostOptions() { } + public System.TimeSpan ShutdownTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} +namespace Microsoft.Extensions.Hosting.Internal +{ + public partial class ApplicationLifetime : Microsoft.Extensions.Hosting.IApplicationLifetime, Microsoft.Extensions.Hosting.IHostApplicationLifetime + { + public ApplicationLifetime(Microsoft.Extensions.Logging.ILogger logger) { } + public System.Threading.CancellationToken ApplicationStarted { get { throw null; } } + public System.Threading.CancellationToken ApplicationStopped { get { throw null; } } + public System.Threading.CancellationToken ApplicationStopping { get { throw null; } } + public void NotifyStarted() { } + public void NotifyStopped() { } + public void StopApplication() { } + } + public partial class ConsoleLifetime : Microsoft.Extensions.Hosting.IHostLifetime, System.IDisposable + { + public ConsoleLifetime(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Hosting.IHostEnvironment environment, Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime, Microsoft.Extensions.Options.IOptions hostOptions) { } + public ConsoleLifetime(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Hosting.IHostEnvironment environment, Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime, Microsoft.Extensions.Options.IOptions hostOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public void Dispose() { } + public System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken) { throw null; } + public System.Threading.Tasks.Task WaitForStartAsync(System.Threading.CancellationToken cancellationToken) { throw null; } + } + public partial class HostingEnvironment : Microsoft.Extensions.Hosting.IHostEnvironment, Microsoft.Extensions.Hosting.IHostingEnvironment + { + public HostingEnvironment() { } + public string ApplicationName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ContentRootPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string EnvironmentName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs b/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs index b0581730290a9..1f2f7d86fea10 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs @@ -12,10 +12,7 @@ namespace Microsoft.Extensions.Hosting.Internal { - internal class Host : IHost -#if DISPOSE_ASYNC - , IAsyncDisposable -#endif + internal class Host : IHost, IAsyncDisposable { private readonly ILogger _logger; private readonly IHostLifetime _hostLifetime; @@ -104,11 +101,7 @@ public async Task StopAsync(CancellationToken cancellationToken = default) _logger.Stopped(); } -#if DISPOSE_ASYNC - public void Dispose() - { - DisposeAsync().GetAwaiter().GetResult(); - } + public void Dispose() => DisposeAsync().GetAwaiter().GetResult(); public async ValueTask DisposeAsync() { @@ -122,11 +115,5 @@ public async ValueTask DisposeAsync() break; } } -#else - public void Dispose() - { - (Services as IDisposable)?.Dispose(); - } -#endif } } diff --git a/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj b/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj index 6c6dfc428a8fc..c452f4f03f28c 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj @@ -2,14 +2,13 @@ .NET Core hosting and startup infrastructures for applications. - netstandard2.0;netcoreapp3.0 + netstandard2.0;netstandard2.1;netcoreapp3.0 netcoreapp3.0 $(NoWarn);CS1591 true hosting true true - $(DefineConstants);DISPOSE_ASYNC @@ -27,4 +26,8 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.AsyncDisposable.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.AsyncDisposable.cs deleted file mode 100644 index 5bd73514f401f..0000000000000 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.AsyncDisposable.cs +++ /dev/null @@ -1,130 +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. - -#if NETCOREAPP - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Xunit; - -namespace Microsoft.Extensions.Hosting.Internal -{ - public partial class HostTests - { - [Fact] - public async Task HostCallsDisposeAsyncOnServiceProvider() - { - using (var host = CreateBuilder() - .ConfigureServices((hostContext, services) => - { - services.AddSingleton(); - }) - .Build()) - { - await host.StartAsync(); - - var asyncDisposableService = host.Services.GetService(); - - Assert.False(asyncDisposableService.DisposeAsyncCalled); - - await host.StopAsync(); - - Assert.False(asyncDisposableService.DisposeAsyncCalled); - - host.Dispose(); - - Assert.True(asyncDisposableService.DisposeAsyncCalled); - } - } - - [Fact] - public async Task HostCallsDisposeAsyncOnServiceProviderWhenDisposeAsyncCalled() - { - using (var host = CreateBuilder() - .ConfigureServices((hostContext, services) => - { - services.AddSingleton(); - }) - .Build()) - { - await host.StartAsync(); - - var asyncDisposableService = host.Services.GetService(); - - Assert.False(asyncDisposableService.DisposeAsyncCalled); - - await host.StopAsync(); - - Assert.False(asyncDisposableService.DisposeAsyncCalled); - - await ((IAsyncDisposable)host).DisposeAsync(); - - Assert.True(asyncDisposableService.DisposeAsyncCalled); - } - } - - [Fact] - public async Task DisposeAsync_DisposesAppConfigurationProviders() - { - var providerMock = new Mock().As(); - providerMock.Setup(d => d.Dispose()); - - var sourceMock = new Mock(); - sourceMock.Setup(s => s.Build(It.IsAny())) - .Returns((ConfigurationProvider)providerMock.Object); - - var host = CreateBuilder() - .ConfigureAppConfiguration(configuration => - { - configuration.Add(sourceMock.Object); - }) - .Build(); - - providerMock.Verify(c => c.Dispose(), Times.Never); - - await ((IAsyncDisposable)host).DisposeAsync(); - - providerMock.Verify(c => c.Dispose(), Times.AtLeastOnce()); - } - - [Fact] - public async Task DisposeAsync_DisposesHostConfigurationProviders() - { - var providerMock = new Mock().As(); - providerMock.Setup(d => d.Dispose()); - - var sourceMock = new Mock(); - sourceMock.Setup(s => s.Build(It.IsAny())) - .Returns((ConfigurationProvider)providerMock.Object); - - var host = CreateBuilder() - .ConfigureHostConfiguration(configuration => - { - configuration.Add(sourceMock.Object); - }) - .Build(); - - providerMock.Verify(c => c.Dispose(), Times.Never); - - await ((IAsyncDisposable)host).DisposeAsync(); - - providerMock.Verify(c => c.Dispose(), Times.AtLeastOnce()); - } - - private class AsyncDisposableService: IAsyncDisposable - { - public bool DisposeAsyncCalled { get; set; } - - public ValueTask DisposeAsync() - { - DisposeAsyncCalled = true; - return default; - } - } - } -} -#endif diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.cs index 886ba12bded19..8876456dce4d4 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/Internal/HostTests.cs @@ -10,13 +10,12 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting.Fakes; using Microsoft.Extensions.Hosting.Tests.Fakes; -using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace Microsoft.Extensions.Hosting.Internal { - public partial class HostTests + public class HostTests { [Fact] public async Task HostInjectsHostingEnvironment() @@ -1028,6 +1027,105 @@ public void Dispose_DisposesHostConfigurationProviders() providerMock.Verify(c => c.Dispose(), Times.AtLeastOnce()); } + [Fact] + public async Task HostCallsDisposeAsyncOnServiceProvider() + { + using (var host = CreateBuilder() + .ConfigureServices((hostContext, services) => + { + services.AddSingleton(); + }) + .Build()) + { + await host.StartAsync(); + + var asyncDisposableService = host.Services.GetService(); + + Assert.False(asyncDisposableService.DisposeAsyncCalled); + + await host.StopAsync(); + + Assert.False(asyncDisposableService.DisposeAsyncCalled); + + host.Dispose(); + + Assert.True(asyncDisposableService.DisposeAsyncCalled); + } + } + + [Fact] + public async Task HostCallsDisposeAsyncOnServiceProviderWhenDisposeAsyncCalled() + { + using (var host = CreateBuilder() + .ConfigureServices((hostContext, services) => + { + services.AddSingleton(); + }) + .Build()) + { + await host.StartAsync(); + + var asyncDisposableService = host.Services.GetService(); + + Assert.False(asyncDisposableService.DisposeAsyncCalled); + + await host.StopAsync(); + + Assert.False(asyncDisposableService.DisposeAsyncCalled); + + await ((IAsyncDisposable) host).DisposeAsync(); + + Assert.True(asyncDisposableService.DisposeAsyncCalled); + } + } + + [Fact] + public async Task DisposeAsync_DisposesAppConfigurationProviders() + { + var providerMock = new Mock().As(); + providerMock.Setup(d => d.Dispose()); + + var sourceMock = new Mock(); + sourceMock.Setup(s => s.Build(It.IsAny())) + .Returns((ConfigurationProvider) providerMock.Object); + + var host = CreateBuilder() + .ConfigureAppConfiguration(configuration => + { + configuration.Add(sourceMock.Object); + }) + .Build(); + + providerMock.Verify(c => c.Dispose(), Times.Never); + + await ((IAsyncDisposable) host).DisposeAsync(); + + providerMock.Verify(c => c.Dispose(), Times.AtLeastOnce()); + } + + [Fact] + public async Task DisposeAsync_DisposesHostConfigurationProviders() + { + var providerMock = new Mock().As(); + providerMock.Setup(d => d.Dispose()); + + var sourceMock = new Mock(); + sourceMock.Setup(s => s.Build(It.IsAny())) + .Returns((ConfigurationProvider) providerMock.Object); + + var host = CreateBuilder() + .ConfigureHostConfiguration(configuration => + { + configuration.Add(sourceMock.Object); + }) + .Build(); + + providerMock.Verify(c => c.Dispose(), Times.Never); + + await ((IAsyncDisposable) host).DisposeAsync(); + + providerMock.Verify(c => c.Dispose(), Times.AtLeastOnce()); + } private IHostBuilder CreateBuilder(IConfiguration config = null) { @@ -1127,5 +1225,16 @@ public Task StopAsync(CancellationToken token) public void Dispose() => _disposing(); } + + private class AsyncDisposableService : IAsyncDisposable + { + public bool DisposeAsyncCalled { get; set; } + + public ValueTask DisposeAsync() + { + DisposeAsyncCalled = true; + return default; + } + } } }