Dependency Injection composition root and decorator pattern

前端 未结 3 2025
不思量自难忘°
不思量自难忘° 2021-02-12 13:42

I\'m getting StackoverflowException\'s in my implementation of the decorator pattern when using dependency injection. I think it is because I\'m \"missing\" somethi

相关标签:
3条回答
  • 2021-02-12 14:09

    Question: I believe I'm missing a key piece of information, which is leading me to StackoverflowExceptions due to circular dependencies. How do I correctly implement my decorator class while still following dependency injection/inversion of control principles and conventions?

    As was already pointed out the best way to do this is with the following construct.

    container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
        new InjectionConstructor(new ResolvedParameter<CustomerService>()));
    

    This allows you to specify how the parameters are resolved by type. You could also do it by name but by type is a cleaner implementation and allows for better checking during compile time as a change or mistype in a string will not be caught. Note that the only minute difference between this code part and the code offered by Mark Seemann is a correction in the spelling of InjectionConstructor. I will not elaborate on this part any more as there is nothing else to add that Mark Seemann has not already explained.


    Second question: What about if I decided I only wanted to apply the logging decorator in certain circumstances? So if I had MyController1 that I wished to have a CustomerServiceLoggingDecorator dependency, but MyController2 only needs a normal CustomerService, how do I create two separate registrations?

    You can do this using the way specified above using the Fluent notation OR using named dependency with a dependency override.

    Fluent

    This registers the controller with the container and specifies an overrload for that type in the constructor. I prefer this approach over the second but it just depends on where you want to specify the type.

    container.RegisterType<MyController2>(
        new InjectionConstructor(new ResolvedParameter<CustomerService>()));
    

    Named dependency

    You do this the exact same way, you register both of them like so.

    container.RegisterType<ICustomerService, CustomerService>("plainService");
    
    container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
        new InjectionConstructor(new ResolvedParameter<CustomerService>()));
    

    The difference here is that you use a named dependency instead for the other types that can be resolved using the same interface. This is because the interface needs to be resolved to exactly one concrete type every time a resolve is done by Unity so you can not have multiple unnamed registered types that are registered to the same interface. Now you can specify an override in your controller constructor using an attribute. My example is for a controller named MyController2 and I added the Dependency attribute with the name also specified above in the registration. So for this constructor a CustomerService type will be injected instead of the default CustomerServiceLoggingDecorator type. MyController1 will still use the default unnamed registration for ICustomerService which is type CustomerServiceLoggingDecorator.

    public MyController2([Dependency("plainService")]ICustomerService service) 
    
    public MyController1(ICustomerService service) 
    

    There are also ways to do this when you manually resolve the type on the container itself, see Resolving Objects by Using Overrides. The problem here is that you need access to the container itself to do this which is not recommended. As an alternative you could create a wrapper around the container that you then inject into the Controller (or other type) and then retrieve a type that way with overrides. Again, this gets a bit messy and I would avoid it if possible.

    0 讨论(0)
  • 2021-02-12 14:12

    Building upon Mark's second answer I'd look to registering the CustomerService with a InjectionFactory and only register it with the service type without it's interface like:

    containter.RegisterType<CustomerService>(new InjectionFactory(
        container => new CustomerService(containter.Resolve<IGenericRepository<Customer>>())));
    

    This would then allow, as in Mark's answer, for you to register the logging object like:

    containter.RegisterType<ICutomerService, CutomerServiceLoggingDecorator>(new InjectionConstructor(
        new ResolvedParameter<CustomerService>()));
    

    This is basically the same technique that I use whenever I require something to be lazily loaded as I don't want my objects to depend upon Lazy<IService> and by wrapping them in proxy allows me to only inject IService but have it resolved lazily through the proxy.

    This will also allow you to pick and choose where either the logging object or the normal object is injected instead of requiring magic strings by simply resolving a CustomerService for your object instead of the ICustomerService.

    For a logging CustomerService:

    container.Resolve<ICustomerService>()

    Or for a non-logging CustomerService:

    container.Resolve<CustomerService>()

    0 讨论(0)
  • 2021-02-12 14:13

    Preamble

    Whenever you are having trouble with a DI Container (Unity or otherwise), ask yourself this: is using a DI Container worth the effort?

    In most cases, the answer ought to be no. Use Pure DI instead. All your answers are trivial to answer with Pure DI.

    Unity

    If you must use Unity, perhaps the following will be of help. I haven't used Unity since 2011, so things may have changed since then, but looking up the issue in section 14.3.3 in my book, something like this might do the trick:

    container.RegisterType<ICustomerService, CustomerService>("custSvc");
    container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
        new InjectionConstructor(
            new ResolvedParameter<ICustomerService>("custSvc")));
    

    Alternatively, you may also be able to do this:

    container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
        new InjectionConstructor(
            new ResolvedParameter<CustomerService>()));
    

    This alternative is easier to maintain because it does not rely on named services, but has the (potential) disadvantage that you can't resolve CustomerService through the ICustomerService interface. You probably shouldn't be doing that anyway, so it ought not to be an issue, so this is probably a better alternative.

    0 讨论(0)
提交回复
热议问题