An Extended Service Provider for .NET

Introduction

In the past, I already wrote about the .NET service provider, including some gotchasrecent changes, and its history. Now, I'm going to talk about something different.

There are multiple open tickets, which include some features, for the .NET service provider. Having experienced some issues myself, I decided to augment the built-in provider with some features that I find useful, so here we are.

Problems

With this implementation, I wanted to overcome a few issues:

  • Being able to provide my own service instance, at runtime (dynamically); note that this is already possible, as you can specify a factory method that will be called when the service is requested, but I wanted a different thing, more dynamic
  • Being able to hook into the service provider and do things to services once they are obtained; this allows, for example, setting property values or doing any kind of instantiation

Let's see how this works, enter the ExtendedServiceProvider!

The ExtendedServiceProvider

What I came up was essentially a wrapper around IServiceProvider, but, this required some extra work for ASP.NET Core. The wrapper itself is represented by this interface:

public interface IExtendedServiceProvider : IKeyedServiceProvider, IServiceProviderIsService, IServiceProviderIsKeyedService, ISupportRequiredService, IServiceScopeFactory
{
}

As you can see, it's essentially a number of interfaces put together, because, the built-in provider also implements these interfaces.

Service Provider Resolver

A resolver is an implementation of the IServiceProviderResolver interface:

public interface IServiceProviderResolver
{
    object? Resolve(IKeyedServiceProvider serviceProvider, Type serviceType, object? serviceKey);
}

You register one or more during service registration time, before the service provider is built:

builder.Services.AddServiceProviderResolver<MyResolver>();
builder.Services.AddServiceProviderResolver<MyOtherResolver>();

The first non-null result returned from the registered service resolvers, for the same service type and key, will be used, which means, will not be returned from the registered services. The resolver classes are, of course, instantiated using dependency injection (DI).

Service Provider Hook

The other extensibility point is a hook, which will be called when a service is instantiated and before it is returned to the caller of the GetService/GetKeyedService/GetRequiredService/GetRequiredKeyedService method. It is specified by the IServiceProviderHook interface:

public interface IServiceProviderHook
{
    void ServiceResolved(Type serviceType, object service);
}

Notice that this may be called multiple times for the same service, each time a service is requested from DI, so you may need to inspect the provided service instance.

To register:

builder.Services.AddServiceProviderHook<MyHook>();

Again, you can specify multiple hooks, and all will be instantiated through DI.

Usage

There are different ways to use the ExtendedServiceProvider, depending on whether we are on a console or a web app.

Console App

For console apps, the only thing we need is to call BuildExtendedServiceProvider on a ServiceCollection instance:

var services = new ServiceCollection();
services.AddSingleton<IMyType, MyType>();
...
var serviceProvider = services.BuildExtendedServiceProvider();

We can pass an optional ServiceProviderOptions to BuildExtendedServiceProvider, but if we don't, one will be passed implicitly with ValidateOnBuild and ValidateScopes both set to true.

Web App

For we apps, it's only slightly more complex, but, all we need is to call UseExtendedServiceProvider on the host:

builder.Host.UseExtendedServiceProvider();

Everything works the same, i.e., all registrations, hooks, and resolvers.

Conclusion

And this is it. I'll probably be making some improvements to this in the future. If you see any value in this, or have any questions, do let me know!

Source code: https://github.com/rjperes/ExtendedServiceProvider

Nuget: https://www.nuget.org/packages/ExtendedServiceProvider


Comments

Popular posts from this blog

Domain Events with .NET

ASP.NET Core Pitfalls – Posting a String

Domain Events with .NET - New Features