未验证 提交 c14ac488 编写于 作者: D David Fowler 提交者: GitHub

Add support for IServiceProviderIsService (#54047)

* Add support for IServiceProviderIsService
- This optional service lets consumers query to see if a service is resolvable without side effects (not having to explicitly resolve the service).
- Added new spec tests to verify the baseline behavior based on IServiceCollection features.
- Handle built in services as part of IsServce
- Special case built in services as part of the IsService check
- Make the tests part of the core DI tests and enable skipping via a property
Co-authored-by: NTravis Illig <tillig@paraesthesia.com>
上级 67b93b2b
......@@ -36,6 +36,10 @@ public partial interface IServiceProviderFactory<TContainerBuilder> where TConta
TContainerBuilder CreateBuilder(Microsoft.Extensions.DependencyInjection.IServiceCollection services);
System.IServiceProvider CreateServiceProvider(TContainerBuilder containerBuilder);
}
public partial interface IServiceProviderIsService
{
bool IsService(System.Type serviceType);
}
public partial interface IServiceScope : System.IDisposable
{
System.IServiceProvider ServiceProvider { get; }
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Optional service used to determine if the specified type is available from the <see cref="IServiceProvider"/>.
/// </summary>
public interface IServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
bool IsService(Type serviceType);
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.DependencyInjection.Specification.Fakes;
using Xunit;
namespace Microsoft.Extensions.DependencyInjection.Specification
{
public abstract partial class DependencyInjectionSpecificationTests
{
public virtual bool SupportsIServiceProviderIsService => true;
[Fact]
public void ExplictServiceRegisterationWithIsService()
{
if (!SupportsIServiceProviderIsService)
{
return;
}
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeService), typeof(FakeService));
var provider = CreateServiceProvider(collection);
// Act
var serviceProviderIsService = provider.GetService<IServiceProviderIsService>();
// Assert
Assert.NotNull(serviceProviderIsService);
Assert.True(serviceProviderIsService.IsService(typeof(IFakeService)));
Assert.False(serviceProviderIsService.IsService(typeof(FakeService)));
}
[Fact]
public void OpenGenericsWithIsService()
{
if (!SupportsIServiceProviderIsService)
{
return;
}
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
var provider = CreateServiceProvider(collection);
// Act
var serviceProviderIsService = provider.GetService<IServiceProviderIsService>();
// Assert
Assert.NotNull(serviceProviderIsService);
Assert.True(serviceProviderIsService.IsService(typeof(IFakeOpenGenericService<IFakeService>)));
Assert.False(serviceProviderIsService.IsService(typeof(IFakeOpenGenericService<>)));
}
[Fact]
public void ClosedGenericsWithIsService()
{
if (!SupportsIServiceProviderIsService)
{
return;
}
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<IFakeService>), typeof(FakeOpenGenericService<IFakeService>));
var provider = CreateServiceProvider(collection);
// Act
var serviceProviderIsService = provider.GetService<IServiceProviderIsService>();
// Assert
Assert.NotNull(serviceProviderIsService);
Assert.True(serviceProviderIsService.IsService(typeof(IFakeOpenGenericService<IFakeService>)));
}
[Fact]
public void IEnumerableWithIsServiceAlwaysReturnsTrue()
{
if (!SupportsIServiceProviderIsService)
{
return;
}
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeService), typeof(FakeService));
var provider = CreateServiceProvider(collection);
// Act
var serviceProviderIsService = provider.GetService<IServiceProviderIsService>();
// Assert
Assert.NotNull(serviceProviderIsService);
Assert.True(serviceProviderIsService.IsService(typeof(IEnumerable<IFakeService>)));
Assert.True(serviceProviderIsService.IsService(typeof(IEnumerable<FakeService>)));
Assert.False(serviceProviderIsService.IsService(typeof(IEnumerable<>)));
}
[Fact]
public void BuiltInServicesWithIsServiceReturnsTrue()
{
if (!SupportsIServiceProviderIsService)
{
return;
}
// Arrange
var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeService), typeof(FakeService));
var provider = CreateServiceProvider(collection);
// Act
var serviceProviderIsService = provider.GetService<IServiceProviderIsService>();
// Assert
Assert.NotNull(serviceProviderIsService);
Assert.True(serviceProviderIsService.IsService(typeof(IServiceProvider)));
Assert.True(serviceProviderIsService.IsService(typeof(IServiceScopeFactory)));
Assert.True(serviceProviderIsService.IsService(typeof(IServiceProviderIsService)));
}
}
}
......@@ -12,7 +12,7 @@
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
internal sealed class CallSiteFactory
internal sealed class CallSiteFactory : IServiceProviderIsService
{
private const int DefaultSlot = 0;
private readonly ServiceDescriptor[] _descriptors;
......@@ -441,6 +441,38 @@ public void Add(Type type, ServiceCallSite serviceCallSite)
_callSiteCache[new ServiceCacheKey(type, DefaultSlot)] = serviceCallSite;
}
public bool IsService(Type serviceType)
{
if (serviceType is null)
{
throw new ArgumentNullException(nameof(serviceType));
}
// Querying for an open generic should return false (they aren't resolvable)
if (serviceType.IsGenericTypeDefinition)
{
return false;
}
if (_descriptorLookup.ContainsKey(serviceType))
{
return true;
}
if (serviceType.IsConstructedGenericType && serviceType.GetGenericTypeDefinition() is Type genericDefinition)
{
// We special case IEnumerable since it isn't explicitly registered in the container
// yet we can manifest instances of it when requested.
return genericDefinition == typeof(IEnumerable<>) || _descriptorLookup.ContainsKey(genericDefinition);
}
// These are the built in service types that aren't part of the list of service descriptors
// If you update these make sure to also update the code in ServiceProvider.ctor
return serviceType == typeof(IServiceProvider) ||
serviceType == typeof(IServiceScopeFactory) ||
serviceType == typeof(IServiceProviderIsService);
}
private struct ServiceDescriptorCacheItem
{
private ServiceDescriptor _item;
......
......@@ -38,8 +38,11 @@ internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, Serv
Root = new ServiceProviderEngineScope(this);
CallSiteFactory = new CallSiteFactory(serviceDescriptors);
// The list of built in services that aren't part of the list of service descriptors
// keep this in sync with CallSiteFactory.IsService
CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
CallSiteFactory.Add(typeof(IServiceScopeFactory), new ServiceScopeFactoryCallSite(Root));
CallSiteFactory.Add(typeof(IServiceProviderIsService), new ConstantCallSite(typeof(IServiceProviderIsService), CallSiteFactory));
if (options.ValidateScopes)
{
......@@ -111,7 +114,9 @@ internal object GetService(Type serviceType, ServiceProviderEngineScope serviceP
Func<ServiceProviderEngineScope, object> realizedService = _realizedServices.GetOrAdd(serviceType, _createServiceAccessor);
OnResolve(serviceType, serviceProviderEngineScope);
DependencyInjectionEventSource.Log.ServiceResolved(serviceType);
return realizedService.Invoke(serviceProviderEngineScope);
var result = realizedService.Invoke(serviceProviderEngineScope);
System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceType));
return result;
}
private void ValidateService(ServiceDescriptor descriptor)
......
......@@ -9,6 +9,8 @@ namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class AutofacDependencyInjectionSpecificationTests : DependencyInjectionSpecificationTests
{
public override bool SupportsIServiceProviderIsService => false;
protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
{
var builder = new ContainerBuilder();
......
......@@ -7,8 +7,10 @@
namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class DryIocDependencyInjectionSpecificationTests: DependencyInjectionSpecificationTests
public class DryIocDependencyInjectionSpecificationTests : DependencyInjectionSpecificationTests
{
public override bool SupportsIServiceProviderIsService => false;
protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
{
return new Container()
......
......@@ -9,6 +9,8 @@ namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class GraceDependencyInjectionSpecificationTests: SkippableDependencyInjectionSpecificationTests
{
public override bool SupportsIServiceProviderIsService => false;
public override string[] SkippedTests => new[]
{
"ResolvesMixedOpenClosedGenericsAsEnumerable",
......
......@@ -7,6 +7,8 @@ namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class LamarDependencyInjectionSpecificationTests : SkippableDependencyInjectionSpecificationTests
{
public override bool SupportsIServiceProviderIsService => false;
public override string[] SkippedTests => new[]
{
"DisposesInReverseOrderOfCreation",
......
......@@ -10,6 +10,8 @@ namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class LightInjectDependencyInjectionSpecificationTests: DependencyInjectionSpecificationTests
{
public override bool SupportsIServiceProviderIsService => false;
protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
{
var builder = new ContainerBuilder();
......
......@@ -7,6 +7,8 @@ namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class StashBoxDependencyInjectionSpecificationTests : DependencyInjectionSpecificationTests
{
public override bool SupportsIServiceProviderIsService => false;
protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
{
return serviceCollection.UseStashbox();
......
......@@ -8,6 +8,8 @@ namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class StructureMapDependencyInjectionSpecificationTests: SkippableDependencyInjectionSpecificationTests
{
public override bool SupportsIServiceProviderIsService => false;
public override string[] SkippedTests => new[]
{
"DisposesInReverseOrderOfCreation",
......
......@@ -7,6 +7,8 @@ namespace Microsoft.Extensions.DependencyInjection.Specification
{
public class UnityDependencyInjectionSpecificationTests: SkippableDependencyInjectionSpecificationTests
{
public override bool SupportsIServiceProviderIsService => false;
// See https://github.com/unitycontainer/microsoft-dependency-injection/issues/87
public override bool ExpectStructWithPublicDefaultConstructorInvoked => true;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册