[英]How do I see all services that a .NET IServiceProvider can provide?
System.IServiceProvider
有一个方法.GetService(Type)
,它返回一个服务。 它本质上是一个将类型映射到服务的字典,它不提供对所有键的访问,可能是因为它旨在通过网络实现。
由实现接口的类来公开允许发现它提供的服务的方法或属性 -没有通用的方法可以单独使用接口查看所有提供的服务。
如果您可以控制服务提供者的来源,请制作一个允许您想要的子界面
interface IBetterServiceProvider : System.IServiceProvider { IList<object> GetAllServices(); IList<Type> GetAllServicedTypes(); }
并使您的服务实现它。
如果您无法控制服务提供者的来源,请转换为IServiceProvider
实现类型,或使用反射来查找告诉您所需内容的属性或方法。 如果您正在使用的提供程序中似乎存在一致的.GetServices()
类型的方法,那么您可以使用动态调度1 、 2 、3来访问该方法而无需强制转换。
也就是说,即使是微软自己的类实现也有点像兔子洞。 引用文档,
IServiceProvider
接口由多种类型实现,包括System.Web.HttpContext
、System.ComponentModel.LicenseContext
、System.ComponentModel.MarshalByValueComponent
和System.ComponentModel.Design.ServiceContainer
。
HttpContext
实现了该接口,但GetService(Type)
方法被记录为仅供内部使用,并且它包含的唯一服务(至少在公共 API 中)是PageInstrumentation
。 在此实现中无法查询所有服务。
ServiceContainer
并没有真正实现接口(尽管它确实有一个该接口类型的内部字段。)尽管ServiceContainer
没有实现接口,但它确实实现了方法, 这有点可怕。 它确实证实了怀疑 - 这是一个将类型映射到服务的美化字典。 同样,这个实现没有提供自己的方式来获取它所拥有的所有服务。 这是我所期望的,因为它明确地是一个服务容器。
LicenseContext.GetService(Type)
只返回 null 除非它被覆盖。 也许这个类的一些子类提供了一种获取所有服务的方法,但这个没有。
我已经完成了对源代码和文档的挖掘。 看起来有点乱,但上面的简短回答是:旧名称或新名称,伪实现或实际实现:无法单独从IServiceProvider
接口获取所有服务,而且我发现的任何 Microsoft 实现都无法为您提供要么这样做。
对于我的应用程序,我想一次迁移我所有的DbContexts
。 打完IServiceCollection
配置和IServiceProvider
建成,我没有得到机会,通过访问他们IServiceProvider
。
下面的代码片段会做到这一点,但是:
这是非常实验性的,因此应实施UnitTest
以注意 Microsoft 的更改并相应地调整方法!
public static class IServiceProviderExtensions
{
/// <summary>
/// Get all registered <see cref="ServiceDescriptor"/>
/// </summary>
/// <param name="provider"></param>
/// <returns></returns>
public static Dictionary<Type, ServiceDescriptor> GetAllServiceDescriptors(this IServiceProvider provider)
{
if (provider is ServiceProvider serviceProvider)
{
var result = new Dictionary<Type, ServiceDescriptor>();
var engine = serviceProvider.GetFieldValue("_engine");
var callSiteFactory = engine.GetPropertyValue("CallSiteFactory");
var descriptorLookup = callSiteFactory.GetFieldValue("_descriptorLookup");
if (descriptorLookup is IDictionary dictionary)
{
foreach (DictionaryEntry entry in dictionary)
{
result.Add((Type)entry.Key, (ServiceDescriptor)entry.Value.GetPropertyValue("Last"));
}
}
return result;
}
throw new NotSupportedException($"Type '{provider.GetType()}' is not supported!");
}
}
public static class ReflectionHelper
{
// ##########################################################################################
// Get / Set Field
// ##########################################################################################
#region Get / Set Field
public static object GetFieldValue(this object obj, string fieldName)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Type objType = obj.GetType();
var fieldInfo = GetFieldInfo(objType, fieldName);
if (fieldInfo == null)
throw new ArgumentOutOfRangeException(fieldName,
$"Couldn't find field {fieldName} in type {objType.FullName}");
return fieldInfo.GetValue(obj);
}
public static void SetFieldValue(this object obj, string fieldName, object val)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Type objType = obj.GetType();
var fieldInfo = GetFieldInfo(objType, fieldName);
if (fieldInfo == null)
throw new ArgumentOutOfRangeException(fieldName,
$"Couldn't find field {fieldName} in type {objType.FullName}");
fieldInfo.SetValue(obj, val);
}
private static FieldInfo GetFieldInfo(Type type, string fieldName)
{
FieldInfo fieldInfo = null;
do
{
fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
type = type.BaseType;
} while (fieldInfo == null && type != null);
return fieldInfo;
}
#endregion
// ##########################################################################################
// Get / Set Property
// ##########################################################################################
#region Get / Set Property
public static object GetPropertyValue(this object obj, string propertyName)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Type objType = obj.GetType();
var propertyInfo = GetPropertyInfo(objType, propertyName);
if (propertyInfo == null)
throw new ArgumentOutOfRangeException(propertyName,
$"Couldn't find property {propertyName} in type {objType.FullName}");
return propertyInfo.GetValue(obj, null);
}
public static void SetPropertyValue(this object obj, string propertyName, object val)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Type objType = obj.GetType();
var propertyInfo = GetPropertyInfo(objType, propertyName);
if (propertyInfo == null)
throw new ArgumentOutOfRangeException(propertyName,
$"Couldn't find property {propertyName} in type {objType.FullName}");
propertyInfo.SetValue(obj, val, null);
}
private static PropertyInfo GetPropertyInfo(Type type, string propertyName)
{
PropertyInfo propertyInfo = null;
do
{
propertyInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
type = type.BaseType;
} while (propertyInfo == null && type != null);
return propertyInfo;
}
#endregion
}
DbContext
示例用法注册所有DbContext
services.AddDbContext<ProductionDbContext>(optionsBuilder => optionsBuilder.UseSqlite($"Data Source={Path.Combine(Directories.Data, "ProductionDb.sqlite")}"), ServiceLifetime.Transient);
services.AddDbContext<ProductionArchiveDbContext>(optionsBuilder => optionsBuilder.UseSqlite($"Data Source={Path.Combine(Directories.Data, "ProductionArchiveDb.sqlite")}"), ServiceLifetime.Transient);
services.AddDbContext<RecipeDbContext>(optionsBuilder => optionsBuilder.UseSqlite($"Data Source={Path.Combine(Directories.Data, "RecipesDb.sqlite")}"), ServiceLifetime.Transient);
services.AddDbContext<SecurityDbContext>(optionsBuilder => optionsBuilder.UseSqlite($"Data Source={Path.Combine(Directories.Data, "SecurityDb.sqlite")}"), ServiceLifetime.Transient);
services.AddDbContext<TranslationDbContext>(optionsBuilder => optionsBuilder.UseSqlite($"Data Source={Path.Combine(Directories.Data, "TranslationDb.sqlite")}"), ServiceLifetime.Transient);
services.AddDbContext<AlarmsDbContext>(optionsBuilder => optionsBuilder.UseSqlite($"Data Source={Path.Combine(Directories.Data, "AlarmsDb.sqlite")}"), ServiceLifetime.Transient);
从您的IServiceProvier
获取它们
var dbContexts = provider.GetAllServiceDescriptors().Where(d => d.Key.IsSubclassOf(typeof(DbContext))).ToList();
请随意扩展这个类或评论错误
因为这仍然是谷歌最重要的建议之一:
现在有一个 nuget 扩展集,您可以从 M$ 下拉它扩展服务提供者并公开几个有用的端点,其中之一是“GetServices”,它根据您提供的类型返回一个 IEnumerable
https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection.Abstractions/
如果您使用的是核心 Web 应用程序,可能有一个简单的解决方案。 这就是我最终做的。
在启动中:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton(services);
}
通过这种方式,您可以将 IServiceCollection 注入任何需要它的类。
如果您使用的是ASP.net Core的DI,则可以安装Microsoft.Extensions.DependencyInjection.Abstractions
包,为您提供所需的方法。
public static class ServiceProviderServiceExtensions
{
public static IServiceScope CreateScope(this IServiceProvider provider);
public static object GetRequiredService(this IServiceProvider provider, Type serviceType);
public static T GetRequiredService<T>(this IServiceProvider provider);
public static T GetService<T>(this IServiceProvider provider);
public static IEnumerable<T> GetServices<T>(this IServiceProvider provider);
public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType);
}
这个问题没有通用的解决方案,如果有任何方法可以用来查找服务列表,它取决于您正在使用的实现。
对于标记扩展:
var rootProvider = (IRootObjectProvider) serviceProvider.GetService(typeof(IRootObjectProvider));
var lineInfo = (IXmlLineInfo) serviceProvider.GetService(typeof(IXmlLineInfo));
var ambientProvider = (IAmbientProvider) serviceProvider.GetService(typeof(IAmbientProvider));
var designerSerializationProvider = (IDesignerSerializationProvider) serviceProvider.GetService(typeof(IDesignerSerializationProvider));
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var uriContext = (IUriContext)serviceProvider.GetService(typeof(IUriContext));
var typeDescriptorContext = (ITypeDescriptorContext) serviceProvider.GetService(typeof(ITypeDescriptorContext));
var valueSerializerContext = (IValueSerializerContext) serviceProvider.GetService(typeof(IValueSerializerContext));
var xamlTypeResolver = (IXamlTypeResolver) serviceProvider.GetService(typeof(IXamlTypeResolver));
var xamlSchemaContextProvider = (IXamlSchemaContextProvider) serviceProvider.GetService(typeof(IXamlSchemaContextProvider));
var xamlNamespaceResolver = (IXamlNamespaceResolver) serviceProvider.GetService(typeof(IXamlNamespaceResolver));
var xamlNameResolver = (IXamlNameResolver) serviceProvider.GetService(typeof(IXamlNameResolver));
var destinationTypeProvider = (IDestinationTypeProvider) serviceProvider.GetService(typeof(IDestinationTypeProvider));
在 Microsoft 文档中: https : //docs.microsoft.com/../service-contexts-with-type-converters-and-markup-extensions
所以这是在 IServiceCollection 上,而不是在 IServiceProvider 上。
我将此添加到“启动”中......所以至少我知道我的应用程序是如何配置 IoC 的。
但我发现它对故障排除非常有用。
我得到了这个工作:
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace MyCompany.Components.DiDebugging
{
[ExcludeFromCodeCoverage]
public static class IocDebugging
{
public static ICollection<ServiceDescriptor> GenerateServiceDescriptors(this IServiceCollection services)
{
ICollection<ServiceDescriptor> returnItems = new List<ServiceDescriptor>(services);
return returnItems;
}
public static string GenerateServiceDescriptorsString(this IServiceCollection services)
{
StringBuilder sb = new StringBuilder();
IEnumerable<ServiceDescriptor> sds = GenerateServiceDescriptors(services).AsEnumerable()
.OrderBy(o => o.ServiceType.FullName);
foreach (ServiceDescriptor sd in sds)
{
sb.Append($"(ServiceDescriptor):");
sb.Append($"FullName='{sd.ServiceType.FullName}',");
sb.Append($"Lifetime='{sd.Lifetime}',");
sb.Append($"ImplementationType?.FullName='{sd.ImplementationType?.FullName}'");
sb.Append(System.Environment.NewLine);
}
string returnValue = sb.ToString();
return returnValue;
}
public static string GeneratePossibleDuplicatesServiceDescriptorsString(this IServiceCollection services)
{
StringBuilder sb = new StringBuilder();
ICollection<DuplicateIocRegistrationHolder> foundDuplicates =
(from t in GenerateServiceDescriptors(services)
where !string.IsNullOrWhiteSpace(t.ServiceType.FullName)
&& !string.IsNullOrWhiteSpace(t.ImplementationType?.FullName)
group t by new
{
ServiceTypeFullName = t.ServiceType.FullName, t.Lifetime,
ImplementationTypeFullName = t.ImplementationType?.FullName
}
into grp
where grp.Count() > 1
select new DuplicateIocRegistrationHolder()
{
ServiceTypeFullName = grp.Key.ServiceTypeFullName,
Lifetime = grp.Key.Lifetime,
ImplementationTypeFullName = grp.Key.ImplementationTypeFullName,
DuplicateCount = grp.Count()
}).ToList();
foreach (DuplicateIocRegistrationHolder sd in foundDuplicates
.OrderBy(o => o.ServiceTypeFullName))
{
sb.Append($"(DuplicateIocRegistrationHolderServiceDescriptor):");
sb.Append($"ServiceTypeFullName='{sd.ServiceTypeFullName}',");
sb.Append($"Lifetime='{sd.Lifetime}',");
sb.Append($"ImplementationTypeFullName='{sd.ImplementationTypeFullName}',");
sb.Append($"DuplicateCount='{sd.DuplicateCount}'");
sb.Append(System.Environment.NewLine);
}
string returnValue = sb.ToString();
return returnValue;
}
public static void LogServiceDescriptors<T>(this IServiceCollection services, ILoggerFactory loggerFactory)
{
string iocDebugging = services.GenerateServiceDescriptorsString();
Func<object, Exception, string> logMsgStringFunc = (a, b) => iocDebugging;
ILogger<T> logger = loggerFactory.CreateLogger<T>();
logger.Log(
LogLevel.Information,
ushort.MaxValue,
string.Empty,
null,
logMsgStringFunc);
Console.WriteLine(iocDebugging);
string iocPossibleDuplicates = GeneratePossibleDuplicatesServiceDescriptorsString(services);
if (!string.IsNullOrWhiteSpace(iocPossibleDuplicates))
{
Func<object, Exception, string> logMsgStringDuplicatesFunc = (a, b) => iocPossibleDuplicates;
logger.Log(
LogLevel.Warning,
ushort.MaxValue,
string.Empty,
null,
logMsgStringDuplicatesFunc);
Console.WriteLine(iocPossibleDuplicates);
}
}
[DebuggerDisplay("ServiceTypeFullName='{ServiceTypeFullName}', Lifetime='{Lifetime}', ImplementationTypeFullName='{ImplementationTypeFullName}', DuplicateCount='{DuplicateCount}'")]
private sealed class DuplicateIocRegistrationHolder
{
public string ServiceTypeFullName { get; set; }
public ServiceLifetime Lifetime { get; set; }
public string ImplementationTypeFullName { get; set; }
public int DuplicateCount { get; set; }
}
}
}
和
我在我的 Startup.cs 中调用它
示例用法:
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MyCompany.Components.DiDebugging;
namespace MyCompany.MyApplicationOne
{
[ExcludeFromCodeCoverage]
public class Startup
{
public Startup(IConfiguration configuration, ILoggerFactory logFactory)
{
this.Configuration = configuration;
this.LogFactory = logFactory;
}
public IConfiguration Configuration { get; private set; }
public ILoggerFactory LogFactory { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
/* wire up all your dependencies */
services.AddScoped<IMyThing, MyThingConcrete>();
/* the below must be LAST after all other IoC registrations */
services.LogServiceDescriptors<Startup>(this.LogFactory);
}
}
这是一个扩展方法,它将返回与指定类型兼容的所有服务:
public static IEnumerable<T> GetAll<T> (this IServiceProvider provider)
{
var site = typeof(ServiceProvider).GetProperty("CallSiteFactory", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(provider);
var desc = site.GetType().GetField("_descriptors", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(site) as ServiceDescriptor[];
var types = desc.Select(s => s.ServiceType).Where(t => typeof(T).IsAssignableFrom(t));
return types.Select(provider.GetRequiredService).Cast<T>();
}
要获取所有服务,请使用provider.GetAll<object>()
。
为了获得更好的性能,请考虑缓存GetProperty
和GetField
反射的结果。
也许有点晚了,但我还需要查看服务是否已注册的功能。 在我的解决方案下面。 我知道这不是可选的,但对我来说很好。
我为IServiceProvider
做了两个扩展方法:
public static class DiExtension
{
private static IServiceCollection _serviceCollection;
public static void AddServiceCollection(this IServiceProvider services, IServiceCollection serviceCollection)
{
_serviceCollection = serviceCollection;
}
public static bool HasService(this IServiceProvider services, Type serviceType)
{
return _serviceCollection.Any(s => s.ServiceType.FullName == serviceType.FullName);
}
}
如您所见,这是我通过调用AddServiceCollection
方法保留对IServiceCollection
的引用的地方。 此方法在Startup.cs
中的Configure
方法中调用,如下所示:
启动文件
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, Domain.Logging.ILogger logger, IServiceProvider serviceProvider)
{
// Notice that I inject IServiceProvider into the method
serviceProvider.AddServiceCollection(_serviceCollection);
}
为了获得对IServiceCollection
的引用,我在ConfigureServices
方法中设置了它:
public void ConfigureServices(IServiceCollection services)
{
// register all the things you need
_serviceCollection = services;
}
而 offcourse _serviceCollection
是 Startup.cs 中的私有字段:
private IServiceCollection _serviceCollection;
现在,当一切都设置好后,我可以使用以下方法检查服务是否已注册:
public class SomeClass
{
private readonly IServiceProvider _serviceProvider;
public SomeClass(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void SomeMethod(Type service)
{
var hasType = _serviceProvider.HasService(service);
// Do what you have to do...
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.