This is a general question regarding .NET
I am given an instance of the IServiceProvider interface, but I have little documentation as to what might be possible to get from it. How would I find out a list of all the services it might provide?
This is a general question regarding .NET
I am given an instance of the IServiceProvider interface, but I have little documentation as to what might be possible to get from it. How would I find out a list of all the services it might provide?
UPDATE: This was originally written in 2015, and things have changed since then. If this answer in still accepted as you're reading, see additional answers below.
System.IServiceProvider
has a single method, .GetService(Type)
, which returns a single service. It's essentially a map from types to services. Critically to your question, it does not provide access to all keys, probably because it's intended for implementation over the wire.
It's up to the class implementing the interface to expose a method or property that allows discovery of the services it provides - there is no general way to see all provided services using the interface alone.
If you have control over the service providers' source, make a child interface that allows what you want
interface IBetterServiceProvider : System.IServiceProvider
{
IList<object> GetAllServices();
IList<Type> GetAllServicedTypes();
}
and make your services implement it.
If you don't have control over the service providers' source, either cast to the IServiceProvider
implementation type, or use reflection to look for properties or methods that tell you what you want. If there appears to be a consistent .GetServices()
sort of method in the providers you're working with, then you can use dynamic dispatch 1, 2, 3 to access that method without casting.
That said, even Microsoft's own implementations of the class are a bit of a rabbit hole. To quote the docs,
The
IServiceProvider
interface is implemented by a number of types, includingSystem.Web.HttpContext
,System.ComponentModel.LicenseContext
,System.ComponentModel.MarshalByValueComponent
, andSystem.ComponentModel.Design.ServiceContainer
.
HttpContext
implements the interface, but the GetService(Type)
method is documented as internal use only, and the only service it contains (in the public API, at least) is PageInstrumentation
. There is no way to query for all services in this implementation.
ServiceContainer
doesn't actually implement the interface (though it does have an internal field of that interface type.) Even though the ServiceContainer
doesn't implement the interface, it
does implement the method, and it's a bit scary. It does confirm suspicions - it's a glorified dictionary mapping types to services. Again, this implementation doesn't provide its own way of getting all services it holds. This is the one I expected to, since it's explicitly a container of services.
LicenseContext.GetService(Type)
just returns null unless its overridden. Perhaps some of this class' subclasses provide a way to get all services, but this one doesn't.
I'm done digging through source and docs. It appears a bit messy, but the short answer above holds: old name or new, pseudoimplementation or actual implementation: there is no way to get all services from the IServiceProvider
interface alone, and none of Microsoft's implementations that I found give you a way to do that either.
There might be a simple solution if you are using Core Web Application. Here's what I ended up doing.
In Startup:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton(services);
}
This way you can inject the IServiceCollection to any class that needs it.
Because this is still one of the top suggestions from google:
There is now a nuget extension set that you can pull down from M$ that extends the service provider and exposes several useful endpoints, one of those is "GetServices" which returns an IEnumerable based on the type you provide
https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection.Abstractions/
For my Application I want to migrate all my DbContexts
at once. So after the IServiceCollection
is configured and an IServiceProvider
is built, I don't got the chance to access them via IServiceProvider
.
The following code snippet will do it, BUT:
It is very experiemental, therefore a UnitTest
should be implemented to note a change from Microsoft and adapt the method accordingly!
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
register all 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);
Get them from your IServiceProvier
var dbContexts = provider.GetAllServiceDescriptors().Where(d => d.Key.IsSubclassOf(typeof(DbContext))).ToList();
Please feel free to extend this class or remark bugs
There is no general solution to this problem, it is dependent on the implementation you are using if there is any method you could use to find the list of services.
Here is an extension method that will return all services compatible with the specified type:
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[];
return desc.Select(s => provider.GetRequiredService(s.ServiceType)).OfType<T>();
}
To get all the services, use provider.GetAll<object>()
.
For better performance, consider caching the results of GetProperty
and GetField
reflections.
For a MarkupExtension:
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));
for some services you can see the services using debugger view value
At Microsoft documentation: https://learn.microsoft.com/../service-contexts-with-type-converters-and-markup-extensions
So this is on the IServiceCollection, not the IServiceProvider.
I add this to "startup".... so at least I know how my app was IoC configured.
But I find it very very useful for troubleshooting.
I got this to work:
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; }
}
}
}
and
and I call it in my Startup.cs
example usage:
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);
}
}
Maybe a little bit late, but I also needed the functionality to see whether a service was registered or not. Below my solution. I know it is not optional, but is fine for me.
I have made two extension methods for 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);
}
}
As you can see is this the place where I keep a reference to IServiceCollection
by invoking the AddServiceCollection
method. This method is called in Startup.cs
in the Configure
method like this:
Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, Domain.Logging.ILogger logger, IServiceProvider serviceProvider)
{
// Notice that I inject IServiceProvider into the method
serviceProvider.AddServiceCollection(_serviceCollection);
}
To get a reference to IServiceCollection
, I set this in the ConfigureServices
method:
public void ConfigureServices(IServiceCollection services)
{
// register all the things you need
_serviceCollection = services;
}
And offcourse _serviceCollection
is a private field in Startup.cs:
private IServiceCollection _serviceCollection;
Now, when everything is set, I can check whether a service is registered by using:
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...
}
}