繁体   English   中英

如何查看 .NET IServiceProvider 可以提供的所有服务?

[英]How do I see all services that a .NET IServiceProvider can provide?

这是关于 .NET 的一般问题

我得到了一个IServiceProvider接口的实例,但我几乎没有关于可以从中获得什么的文档。 我如何找到它可能提供的所有服务的列表?

System.IServiceProvider有一个方法.GetService(Type) ,它返回一个服务。 它本质上是一个将类型映射到服务的字典,它不提供对所有键的访问,可能是因为它旨在通过网络实现。

实现接口的类来公开允许发现它提供的服务的方法或属性 -没有通用的方法可以单独使用接口查看所有提供的服务。

解决方案:

  • 如果您可以控制服务提供者的来源,请制作一个允许您想要的子界面

     interface IBetterServiceProvider : System.IServiceProvider { IList<object> GetAllServices(); IList<Type> GetAllServicedTypes(); }

    并使您的服务实现它。

  • 如果您无法控制服务提供者的来源,请转换为IServiceProvider实现类型,或使用反射来查找告诉您所需内容的属性或方法。 如果您正在使用的提供程序中似乎存在一致的.GetServices()类型的方法,那么您可以使用动态调度123来访问该方法而无需强制转换。


也就是说,即使是微软自己的类实现也有点像兔子洞。 引用文档,

IServiceProvider接口由多种类型实现,包括System.Web.HttpContextSystem.ComponentModel.LicenseContextSystem.ComponentModel.MarshalByValueComponentSystem.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>()

为了获得更好的性能,请考虑缓存GetPropertyGetField反射的结果。

也许有点晚了,但我还需要查看服务是否已注册的功能。 在我的解决方案下面。 我知道这不是可选的,但对我来说很好。

我为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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM