How to overwrite a scoped service with a decorated implementation?

后端 未结 5 591
日久生厌
日久生厌 2020-12-18 06:07

I\'m trying to write an ASP.NET Core 2.2 integration test, where the test setup decorates a specific service that would normally be available to the API as a dependency. The

5条回答
  •  Happy的楠姐
    2020-12-18 06:41

    Contrary to popular belief, the decorator pattern is fairly easy to implement using the built-in container.

    What we generally want is to overwrite the registration of the regular implementation by the decorated one, making us of the original one as a parameter to the decorator. As a result, asking for an IDependency should lead to a DecoratorImplementation wrapping the OriginalImplementation.

    (If we merely want to register the decorator as a different TService than the original, things are even easier.)

    public void ConfigureServices(IServiceCollection services)
    {
        // First add the regular implementation
        services.AddSingleton();
    
        // Wouldn't it be nice if we could do this...
        services.AddDecorator(
            (serviceProvider, decorated) => new DecoratorImplementation(decorated));
                
        // ...or even this?
        services.AddDecorator();
    }
    

    The above code works once we add the following extension methods:

    public static class DecoratorRegistrationExtensions
    {
        /// 
        /// Registers a  decorator on top of the previous registration of that type.
        /// 
        /// Constructs a new instance based on the the instance to decorate and the .
        /// If no lifetime is provided, the lifetime of the previous registration is used.
        public static IServiceCollection AddDecorator(
            this IServiceCollection services,
            Func decoratorFactory,
            ServiceLifetime? lifetime = null)
            where TService : class
        {
            // By convention, the last registration wins
            var previousRegistration = services.LastOrDefault(
                descriptor => descriptor.ServiceType == typeof(TService));
    
            if (previousRegistration is null)
                throw new InvalidOperationException($"Tried to register a decorator for type {typeof(TService).Name} when no such type was registered.");
    
            // Get a factory to produce the original implementation
            var decoratedServiceFactory = previousRegistration.ImplementationFactory;
            if (decoratedServiceFactory is null && previousRegistration.ImplementationInstance != null)
                decoratedServiceFactory = _ => previousRegistration.ImplementationInstance;
            if (decoratedServiceFactory is null && previousRegistration.ImplementationType != null)
                decoratedServiceFactory = serviceProvider => ActivatorUtilities.CreateInstance(
                    serviceProvider, previousRegistration.ImplementationType, Array.Empty());
    
            var registration = new ServiceDescriptor(
                typeof(TService), CreateDecorator, lifetime ?? previousRegistration.Lifetime);
    
            services.Add(registration);
    
            return services;
    
            // Local function that creates the decorator instance
            TService CreateDecorator(IServiceProvider serviceProvider)
            {
                var decoratedInstance = (TService)decoratedServiceFactory(serviceProvider);
                var decorator = decoratorFactory(serviceProvider, decoratedInstance);
                return decorator;
            }
        }
    
        /// 
        /// Registers a  decorator on top of the previous registration of that type.
        /// 
        /// If no lifetime is provided, the lifetime of the previous registration is used.
        public static IServiceCollection AddDecorator(
            this IServiceCollection services,
            ServiceLifetime? lifetime = null)
            where TService : class
            where TImplementation : TService
        {
            return AddDecorator(
                services,
                (serviceProvider, decoratedInstance) =>
                    ActivatorUtilities.CreateInstance(serviceProvider, decoratedInstance),
                lifetime);
        }
    }
    
        

    提交回复
    热议问题