I recently found myself, having the need of intercepting calls to a service from an external NuGet package. The Service was registered in the Service Collection via extension methods and I wanted it to be "intact", seen to configuration and dependencies etc. I needed to register a proxy/decorator, with the same lifetime, so that I could intercept the calls. Hence why services.Replace
failed for me.
What I wanted was to be able to do something like the following (please guide me if there's already built in support for this):
Given:
services.AddExternalService();
//Simulated via:
// services.AddScoped<IExternalService, ExternalService>();
I wanted to be able to do:
services.Proxy<IExternalService>(r => new InterceptingService(
r.ServiceProvider.GetService<ILogger<InterceptingService>>(),
r.Proxied));
Solution
ServiceDescriptor
to the rescue. All I needed to do was something in the lines of below (sample code quality, adapt to your needs). It might look a bit intimidating, but that's just because there are a bunch of overloads, you can of course reduce it if you want.
public static class ServiceProxyExtensions
{
public static IServiceCollection Proxy(
this IServiceCollection services,
ServiceDescriptor toProxy,
Func<(IServiceProvider ServiceProvider, Type ProxiedType), object> proxyServiceFactory)
{
var proxiedType = toProxy.ImplementationType ?? throw new Exception(
$"Can not register proxy service, as no implementation type was found for {toProxy.ServiceType}");
services.Remove(toProxy);
services.Add(ServiceDescriptor.Describe(proxiedType, proxiedType, toProxy.Lifetime));
services.Add(ServiceDescriptor.Describe(toProxy.ServiceType, sp => proxyServiceFactory((sp, proxiedType)),
toProxy.Lifetime));
return services;
}
public static IServiceCollection Proxy(
this IServiceCollection services,
Type serviceType,
Type proxiedType,
Func<(IServiceProvider ServiceProvider, Type ProxiedType), object> proxyServiceFactory)
{
var toProxy = services.Single(sd =>
sd.ServiceType == serviceType &&
sd.ImplementationType == proxiedType);
return services.Proxy(toProxy, proxyServiceFactory);
}
public static IServiceCollection Proxy(
this IServiceCollection services,
Type serviceType,
Func<(IServiceProvider ServiceProvider, Type ProxiedType), object> proxyServiceFactory)
{
var toProxy = services.Single(sd =>
sd.ServiceType == serviceType);
return services.Proxy(toProxy, proxyServiceFactory);
}
public static IServiceCollection Proxy<TService, TProxied>(
this IServiceCollection services,
Func<(IServiceProvider ServiceProvider, TService Proxied), TService> proxyServiceFactory)
=> services.Proxy(typeof(TService), typeof(TProxied), r =>
{
var proxied = (TService) r.ServiceProvider.GetService(r.ProxiedType);
return proxyServiceFactory((r.ServiceProvider, proxied));
});
public static IServiceCollection Proxy<TService>(
this IServiceCollection services,
Func<(IServiceProvider ServiceProvider, TService Proxied), TService> proxyServiceFactory)
=> services.Proxy(typeof(TService), r =>
{
var proxied = (TService) r.ServiceProvider.GetService(r.ProxiedType);
return proxyServiceFactory((r.ServiceProvider, proxied));
});
}
This allows me to do:
Option 1:
services.Proxy(
typeof(IExternalService),
typeof(ExternalService),
r => new InterceptingService(
r.ServiceProvider.GetService<ILogger<InterceptingService>>(),
(IExternalService) r.ServiceProvider.GetService(r.ProxiedType)));
Option 2:
services.Proxy(
typeof(IExternalService),
r => new InterceptingService(
r.ServiceProvider.GetService<ILogger<InterceptingService>>(),
(IExternalService) r.ServiceProvider.GetService(r.ProxiedType)));
Option 3:
services.Proxy<IExternalService, ExternalService>(r => new InterceptingService(
r.ServiceProvider.GetService<ILogger<InterceptingService>>(),
r.Proxied));
Option 4:
services.Proxy<IExternalService>(r => new InterceptingService(
r.ServiceProvider.GetService<ILogger<InterceptingService>>(),
r.Proxied));
So if we has something like these services:
public interface IExternalService
{
void Execute<T>(T input);
}
public class ExternalService : IExternalService
{
private readonly ILogger<ExternalService> _logger;
public ExternalService(ILogger<ExternalService> logger)
{
_logger = logger;
}
public void Execute<T>(T input)
{
_logger.LogInformation("In external service doing normal stuff");
}
}
public class InterceptingService : IExternalService
{
private readonly ILogger<InterceptingService> _logger;
private readonly IExternalService _innerExternalService;
public InterceptingService(
ILogger<InterceptingService> logger,
IExternalService innerExternalService)
{
_logger = logger;
_innerExternalService = innerExternalService;
}
public void Execute<T>(T input)
{
_logger.LogInformation("In intercepting service");
_innerExternalService.Execute(input);
}
}
We could use it in e.g an Controller
:
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
private readonly ILogger<MyController> _logger;
private readonly IExternalService _externalService;
public MyController(ILogger<MyController> logger, IExternalService externalService)
{
_logger = logger;
_externalService = externalService;
}
[HttpGet]
public void Get()
{
_logger.LogInformation("Controller invoking service.");
_externalService.Execute(42);
}
}
And the output would be:
info: DiProxy.Controllers.MyController[0]
Controller invoking service.
info: DiProxy.Services.InterceptingService[0]
In intercepting service
info: DiProxy.Services.ExternalService[0]
In external service doing normal stuff
Hope you can use it to something. Please, feel free to guide me if it's already built-in.
//Daniel