Dependency injection for non-orchard dlls

Topics: Customizing Orchard, Writing modules
Aug 18, 2012 at 4:45 PM

Hi All,

 

I have a series of services that are in external dlls, which I would like to use in Orchard, via dependency injection. They all follow the form:

 

namespace Services
{
    public interface ICoolService
    {
        GeCoolResponse GetCool(GetCoolRequest request);
    }
}

 

public class ImplCoolService : ICoolService
    {
        private readonly IServiceSettings _serviceSettings;
        private readonly IJsonRequestHelper _jsonRequestHelper;

        public ImplCoolService(IServiceSettings serviceSettings, IJsonRequestHelper requestHelper)
        {
            _serviceSettings = serviceSettings;
            _jsonRequestHelper = requestHelper;
        }

        public GetCoolResponse GetCool(GetCoolRequest request)
        {
            GetCoolResponse response = new GetCoolResponse();
             //...
            return response;
        }
    }
}

These services are nice, and all take interfaces in the constructor, so good (I hope) for DI. 

I would like to use these in Orchard, but to do so, I need to inherit from IDependency. I find myself writing the following additional code in my module:

namespace Product.Orchard.Services
{
    public interface IOrchardCoolService: ICoolService, IDependency
    {
        
    }
}

namespace Product.Orchard.Services
{
    public class OrchardCoolService : IOrchardCoolService
    {
        private readonly CoolService _coolService;

        public OrchardCoolService(IOrchardServiceSettings serviceSettings, IOrchardJsonRequestHelper jsonRequestHelper)
        {
            //arg...a "new". Having to instantiate the underlying class
            //from my other dll, wrapped in this class
            _coolService = new CoolService(serviceSettings, jsonRequestHelper);
        }

        public GetCoolResponse GetCool(GetCoolRequest request)
        {
            return _coolService.GetCool(request);
        }
    }
}

Clearly i also need wrapping interfaces/implementations for my constructor dependencies IServiceSettings and IJsonRequestHelper. Add this up across many real services, with many real methods and many constructore injected dependencies, and there is a lot of wrapping code required, just to ensure that each implementation is inheriting from IDependency for Orchard.

Am I missing a trick? Is there a slicker way to do this (without putting code that really should not depend on Orchard directly into Orchard modules)?

I'm about to embark on a project that will be creating many services that will follow this basic pattern, I really want to get the first ones correct, to allow a good reference implementation for the team.

Many thanks,

 

Paul

 

Aug 20, 2012 at 9:15 AM

Does anyone have any advice on this?

Aug 20, 2012 at 11:11 AM

One way that you can get around not inheriting from IDependency is to write an autofac module in your Orchard module which registers the dependencies.  You'll need to reference your project to Autofac and then create a class which inherits from Autofac.Module.  You can then override the Load function to get the Autofac builder object (within the 'shell' lifetimescope) to register your dependencies.  For example:

public class MyAutofacModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<OrchardCoolService>().As<IOrchardCoolService>();
    }
}

Aug 20, 2012 at 7:50 PM

Brilliant! ....did I miss the documentation on this one? I sure would like to read up on this. Thanks a lot David.

Aug 21, 2012 at 9:19 AM
Edited Aug 21, 2012 at 9:21 AM

Ok, thanks a lot David, that has really made things better. How might you apply this to decorated classes (e.g)

public class CachedCoolService : ICoolService
{
   public CachedCoolService(ICoolService serviceToCache)
   {
      ....
   }
}

I can see that I would want 

builder.RegisterType<CachedCoolService>().As<ICoolService>();

but can I tell Autofac that I want to use a CoolService implementation to satisfy the dependency in the constructor?

Aug 21, 2012 at 9:27 AM

One other thought, are those registrations scoped globally, or local to the module?

Developer
Aug 25, 2012 at 2:08 AM

These registrations are scoped globally, not local to the module.

As for your other question, I don't know. If registering ICoolService multiple times doesn't throw an exception, I guess it will inject the first implementation that is registered. Perhaps there are attributes or other Autofac features available for specifying which service to use under certain conditions. I'd check the Autofac docs for these.

Sep 4, 2012 at 4:59 PM

Sorry for the delay in coming back - I have been on vacation.

The registrations are effectively scoped globally - they are technically scoped to the Shell lifetime rather than the root scope.

Decorators are quite possible with autofac:

builder.RegisterType<MyCoolService>()
    .Named<ICoolService>("mycool");

builder.Register(c => new CachedCoolService(c.ResolveNamed<ICommandHandler>("mycool"))
    .As<ICoolService>();
Alternatively, there is now in Autofac specific decorator support - http://nblumhardt.com/2011/01/decorator-support-in-autofac-2-4/

Hope this helps

Sep 4, 2012 at 5:02 PM

For what it's worth, I've found Mark Seemann's Dependency Injection in .Net very helpful in getting my head round some of this

Sep 4, 2012 at 9:26 PM

Hi David,

 

thanks for the further efforts. I've got that book, so I think it is time to revisit it :D

 

many thanks

Sep 11, 2012 at 1:35 PM
Edited Sep 11, 2012 at 3:11 PM

Well, I've moved on somewhat (after being distracted with other things).

I can decorate my class quite easily if the constructor just takes an ICoolService dependency. However, when I start adding some Orchard Interfaces into the constructor (as below), I get some errors.

builder.RegisterType().Named("cool_implementor");
builder.RegisterDecorator((c, inner)  
                => new CachedCoolService(inner, 
                    c.Resolve<ICacheManager>(), 
                    c.Resolve<IClock>(), 
                    c.Resolve<ICacheSettings>()), fromKey: "cool_implementor");

This gives me

 

None of the constructors found with 'Public binding flags' on type 'Orchard.Caching.DefaultCacheManager' can be invoked with the available services and parameters:

Cannot resolve parameter 'System.Type component' of constructor 'Void .ctor(System.Type, Orchard.Caching.ICacheHolder)'.

 

at run time. The message is clear enough - it doesn't understand how to resolve the parameters of DefaultCacheManager. My question is why? It can resolve DefaultCacheManager well enough when I'm not doing this decoration. Does this mean I have to suddenly explicitly register Orchard implementations against their interfaces? Surely not?

Any Autofac/orchard genius' care to shed any light? I'm a little baffled.

Sep 11, 2012 at 3:16 PM
Edited Sep 11, 2012 at 3:16 PM

I've investigated further, and it seems to be specifically an issue to do with DefaultCacheManager, as it has a Type parameter. From what I understand (I'm very new to autofac) this is a weakly resolved reference. I've confirmed that this is the issue by changing the constructor  on my decorator to remove the ICacheManager dependency. The code below works in that case:

 

builder.RegisterType().Named("cool_implementor");
builder.RegisterDecorator((c, inner)  
                => new CachedCoolService(inner, 
                    //c.Resolve<ICacheManager>(), 
                    c.Resolve<IClock>(), 
                    c.Resolve<ICacheSettings>()), fromKey: "cool_implementor");

 

I'm not sure how to sort this issue though, as presumably you need to know the Type to register the decorator correctly.

So I think I know the problem, but have no idea how to resolve it.

Sep 11, 2012 at 5:22 PM

I'm not sure, but wonder if it is related to Autofac issue 218 as where DefaultCacheManager is registered in src\Orchard\Caching\CacheModule.cs it relies on AttachToComponentRegistration.  I'm also rather new to autofac I'm afraid however, so hoping someone else can jump in with some expertise!

AttachToComponentRegistrationAttachToComponentRegistration
Sep 12, 2012 at 10:33 AM

I've found a way round this problem, so I hope this is useful to everyone in the same scenario:

Way 1: "Poor man's decorators"

Use a new interface that inherits from the previous interface, just for the purposes of the decoration (which of course only works if you want to decorate once - really this is more of an adapter?)

public interface ICachedCoolService: ICoolService, IDependency
{
}

public class CachedCoolService: ICachedCoolService
{
    //implementation
}

Way 2: Use Autofac's "WithParameter" (Better)

Using "WithParameter" is a much better way to do this, and I think should allow easy chaining of decorators

builder.RegisterType<CoolService>().Named<ICoolService>("implementor");
            
builder.RegisterType<CachedCoolService>().As<ICoolService>().WithParameter(
                (p,c) => p.ParameterType == typeof(ICoolService),
                (p,c) => c.ResolveNamed<ICoolService("implementor"));

Oct 17, 2012 at 8:04 PM

Hello, can someone assist me, I can't get my custom module's autofac module (custommodule.cs) to run:

using Autofac;

namespace custom
{
    class customModule : Autofac.Module
    {
  
            protected override void Load(ContainerBuilder builder)
            {
                builder.RegisterType().As();
                builder.RegisterType().As();
            }
        
    }
}

Oct 17, 2012 at 8:34 PM

oops, fixed - the class was not public... sorry about that.

Oct 18, 2012 at 8:40 AM

also - better to raise a new discussion, rather than bomb into another discussion where the last post is a resolution ;)