How To Scope Individual Libraries Using Auto Registration

一个人想着一个人 提交于 2020-01-16 00:45:00

问题


Is it possible to set/change the dependency lifetime scoping options (Transient, Single, PerWebReqest, etc...) for one, two or x number of specific assemblies if you're auto registering/discovering your dependencies? I have sample code below:

public class RegistrationPackage : IPackage
{
    public void RegisterServices(Container container)
    {
        var @namespace = "ConsoleLib";
    var assemblies = AppDomain.CurrentDomain.GetAssemblies()
        .Where(a => a.FullName.StartsWith(@namespace))
        .Select(a => a).ToList();

    var names = assemblies.Select(a => a.FullName);

    Action<string> poormanslogger = Console.WriteLine;
    //poormanslogger = Trace.WriteLine;

    foreach (var name in names)
        poormanslogger("Located the following assemblies: " + name);

    // could combine these two and remove the if statement for the open generic registrations below
    //container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), AppDomain.CurrentDomain.GetAssemblies());

    foreach (var assembly in assemblies)
        foreach (var type in assembly.GetExportedTypes().Where(a => !a.IsAbstract))
        {
            poormanslogger(string.Format("Exported Type: {0}", type.Name));
            foreach (var @interface in type.GetInterfaces())
            {
                poormanslogger(string.Format("\tInterface type: {0}", @interface.Name));
                // register open generic
                if (@interface.IsGenericType && !type.IsAbstract && type.IsGenericType)
                    container.RegisterManyForOpenGeneric(type, assemblies);
                // register closed generic
                if (@interface.IsGenericType && !type.IsAbstract && !type.IsGenericType)
                    container.Register(@interface, type, Lifestyle.Transient);
                // register non generic
                if (!type.IsGenericType && !@interface.IsGenericType && !type.IsAbstract)
                    container.Register(@interface, type, Lifestyle.Transient);
            }

            // unregistered type fall back...
            //container.RegisterOpenGeneric(typeof(ICommandHandler<>), typeof(UnRegisteredCommandHandler<>));
        }

    // The decorators are applied in the order in which they are registered
    container.RegisterDecorator(typeof(ICommandHandler<>),
            typeof(TransactionCommandHandlerDecorator<>));

    container.RegisterDecorator(typeof(ICommandHandler<>),
        typeof(DeadlockRetryCommandHandlerDecorator<>));

    container.RegisterDecorator(typeof(ICommandHandler<>),
        typeof(ValidationCommandHandlerDecorator<>));

    container.RegisterDecorator(typeof(ICommandHandler<>),
        typeof(SecurityCommandHandlerDecorator<>));
}
}

Then I have the standard stuff:

    var container = new Container();

    container.RegisterPackages();

    container.Verify();

    var instance = container.GetInstance<ICommandHandler<UnregisteredCommand>>();
    instance.Handle(new UnregisteredCommand());

Could I possibly use the ExpressionBuilding event?


回答1:


The batch-registration facilities that Simple Injector offers limits itself to registration of generic types. The documentation explains why:

Although many other DI libraries contain an advanced API for doing convention based registration, we found that doing this with custom LINQ queries is easier to write, more understandable, and can often prove to be more flexible than using a predefined and restrictive API.

But even for batch-registrations for types of a generic interface there's no built-in support to differentiate between lifestyles. This is quite deliberate, since this would force you to place library defined attributes on your classes to describe the lifestyle, and this would make your application dependent on Simple Injector, which is something we want to prevent.

Simple Injector 2.6 introduces a new extension point called the lifestyle selection behavior. This extension point allows you to override the default selection behavior of the framework when no lifestyle is supplied explicitly. The default behavior is to use Lifestyle.Transient.

If you need something like this, you should override the default lifestyle selection behavior and create an attribute that you can apply to your components. That could look something like this:

public enum CreationPolicy { Transient, Scoped, Singleton };

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface,
    Inherited = false, AllowMultiple = false)]
public sealed class CreationPolicyAttribute : Attribute {
    public CreationPolicyAttribute(CreationPolicy policy) {
        this.Policy = policy;
    }

    public CreationPolicy Policy { get; private set; }
}

This attribute should be defined in a base project of your application so each class can be marked with it:

[CreationPolicy(CreationPolicy.Scoped)]
public class CustomerRepository : IRepository<Customer>
{
    public CustomerRepository(DbContext context) { }

    public Customer GetById(Guid id) {
        // etc.
    }
}

Using this defined attribute, we can now define our custom lifestyle selection behavior as follows:

public class AttributeLifestyleSelectionBehavior : ILifestyleSelectionBehavior {
    private const CreationPolicy DefaultPolicy = CreationPolicy.Transient;
    private readonly ScopedLifestyle scopedLifestyle;

    public AttributeLifestyleSelectionBehavior (ScopedLifestyle scopedLifestyle) {
        this.scopedLifestyle = scopedLifestyle;
    }

    public Lifestyle SelectLifestyle(Type serviceType, Type implementationType) {
       var attribute = implementationType.GetCustomAttribute<CreationPolicyAttribute>()
           ?? serviceType.GetCustomAttribute<CreationPolicyAttribute>();

       var policy = attribute == null ? DefaultPolicy : attribute.Policy;

       switch (policy) {
           case CreationPolicy.Singleton: return Lifestyle.Singleton;
           case CreationPolicy.Scoped: return this.scopedLifestyle;
           default: return Lifestyle.Transient;
       }
    }
}

This custom behavior can be registered as follows:

var container = new Container();

// This is the scoped lifestyle for our application type.
ScopedLifestyle scopedLifestyle = new WebRequestLifestyle();

container.Options.LifestyleSelectionBehavior =
    new AttributeLifestyleSelectionBehavior(scopedLifestyle);

With this in place we simplified the batch registration, and we don't have to do anything special to select the proper lifestyle:

container.RegisterOpenGeneric(
    typeof(ICommandHandler<>),
    AppDomain.CurrentDomain.GetAssemblies());

var registrations =
    from assembly in assemblies
    from type in assembly.GetExportedTypes()
    where !type.IsAbstract && !type.IsGenericType
    from @interface in type.GetInterfaces()
    where !@interface.IsGenericType
    select new { Service = @interface, Implementation = type };

foreach (var registration in registrations)
{
    container.Register(registration.Service, registration.Implementation);
}

Instead of overriding the lifestyle selection behavior, another option is to have everything register as transient (since that would be a sensible default), but override those few registrations that should be registered otherwise:

container.RegisterOpenGeneric(
    typeof(ICommandHandler<>),
    AppDomain.CurrentDomain.GetAssemblies());

container.Options.EnableOverridingRegistrations = true;

container.Register<ICommandHandler<SpecialOperation>, SpecialOperationHandler>(
    scopedLifestyle);

container.Options.EnableOverridingRegistrations = false;


来源:https://stackoverflow.com/questions/25189347/how-to-scope-individual-libraries-using-auto-registration

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!