Factory Pattern with Open Generics

前端 未结 4 447
南旧
南旧 2020-12-13 18:12

In ASP.NET Core, one of the things you can do with Microsoft\'s dependency injection framework is bind \"open generics\" (generic types unbound to a concrete type) like so:<

4条回答
  •  自闭症患者
    2020-12-13 18:42

    I was dissatisfied with the existing solutions as well.

    Here is a full solution, using the built-in container, that supports everything we need:

    • Simple dependencies.
    • Complex dependencies (requiring the IServiceProvider to be resolved).
    • Configuration data (such as connection strings).

    We will register a proxy of the type that we really want to use. The proxy simply inherits from the intended type, but gets the "difficult" parts (complex dependencies and configuration) through a separately registered Options type.

    Since the Options type is non-generic, it is easy to customize as usual.

    public static class RepositoryExtensions
    {
        /// 
        /// A proxy that injects data based on a registered Options type.
        /// As long as we register the Options with exactly what we need, we are good to go.
        /// That's easy, since the Options are non-generic!
        /// 
        private class ProxyRepository : Repository
        {
            public ProxyRepository(Options options, ISubdependency simpleDependency)
                : base(
                    // A simple dependency is injected to us automatically - we only need to register it
                    simpleDependency,
                    // A complex dependency comes through the non-generic, carefully registered Options type
                    options?.ComplexSubdependency ?? throw new ArgumentNullException(nameof(options)),
                    // Configuration data comes through the Options type as well
                    options.ConnectionString)
            {
            }
        }
    
        public static IServiceCollection AddRepositories(this ServiceCollection services, string connectionString)
        {
            // Register simple subdependencies (to be automatically resolved)
            services.AddSingleton();
    
            // Put all regular configuration on the Options instance
            var optionObject = new Options(services)
            {
                ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString))
            };
    
            // Register the Options instance
            // On resolution, last-minute, add the complex subdependency to the options as well (with access to the service provider)
            services.AddSingleton(serviceProvider => optionObject.WithSubdependency(ResolveSubdependency(serviceProvider)));
    
            // Register the open generic type
            // All dependencies will be resolved automatically: the simple dependency, and the Options (holding everything else)
            services.AddSingleton(typeof(IRepository<>), typeof(ProxyRepository<>));
    
            return services;
    
            // Local function that resolves the subdependency according to complex logic ;-)
            ISubdependency ResolveSubdependency(IServiceProvider serviceProvider)
            {
                return new Subdependency();
            }
        }
    
        internal sealed class Options
        {
            internal IServiceCollection Services { get; }
    
            internal ISubdependency ComplexSubdependency { get; set; }
            internal string ConnectionString { get; set; }
    
            internal Options(IServiceCollection services)
            {
                this.Services = services ?? throw new ArgumentNullException(nameof(services));
            }
    
            /// 
            /// Fluently sets the given subdependency, allowing to options object to be mutated and returned as a single expression.
            /// 
            internal Options WithSubdependency(ISubdependency subdependency)
            {
                this.ComplexSubdependency = subdependency ?? throw new ArgumentNullException(nameof(subdependency));
                return this;
            }
        }
    }
    

提交回复
热议问题