AndrewC
AndrewC

Reputation: 461

Autofac with nested open generics

I'm new to Autofac and having some trouble with resolving a nested open generic service type.

I would like my ContactService to implement two interfaces. Both interfaces make use of the same generic type parameter, but the second is nested:

class ContactService<TParent>
    : IContactService<TParent>,
      IFeatureProvider<ContactFeature<TParent>>
    where TParent : class, IDomainModel
{ }

If I do this:

builder
    .RegisterGeneric(typeof(ContactService<>))
    .As(typeof(IContactService<>))
    .As(typeof(IFeatureProvider<>);

Then trying to resolve the service IFeatureProvider<ContactFeature<Household>> (for example) causes Autofac to attempt to instantiate

ContactService<ContactFeature<Household>>

instead of

ContactService<Household>

I understand why the above doesn't work. So then I tried this instead:

builder
    .RegisterGeneric(typeof(ContactService<>))
    .As(typeof(IContactService<>))
    .As(typeof(IFeatureProvider<>)
        .MakeGenericType(typeof(ContactFeature<>)));

However, I still get a similar error:

TypeLoadException: GenericArguments[0], 'ContactFeature`1[Household]', 
  on 'Contact`1[TParent]' violates the constraint of type parameter 'TParent'.

System.RuntimeTypeHandle.Instantiate(RuntimeTypeHandle handle, IntPtr* pInst, 
  Int32 numGenericArgs, ObjectHandleOnStack type)

ArgumentException: GenericArguments[0], 'ContactFeature`1[Household]', 
  on 'IContactService`1[TParent]' violates the constraint of type 'TParent'.

System.RuntimeType.ValidateGenericArguments(MemberInfo definition, 
  RuntimeType[] genericArguments, Exception e)

It looks like Autofac is trying to pass ContactFeature<Household> as TParent instead of just Household.

Sorry for the long explanation. I hope someone can help me get this to work!

Thank you.

Upvotes: 1

Views: 533

Answers (1)

tdragon
tdragon

Reputation: 3329

It seems all you have to do is to split you open generic registration to 2 separate registrations, like that:

var builder = new ContainerBuilder();
builder.RegisterGeneric(typeof(ContactService<>))
    .As(typeof(IContactService<>));

builder.RegisterGeneric(typeof(ContactService<>))
    .As(typeof(IFeatureProvider<>));

var container = builder.Build();

var service = container.Resolve(typeof(IFeatureProvider<ContactFeature<Household>>));
var service2 = container.Resolve(typeof(IContactService<Household>));

Console.WriteLine(service.GetType());
Console.WriteLine(service2.GetType());

Console.ReadKey();

Both resolves would give you instances of ContactService<Household>. But to be honest, I do not know why it works separately, and does not work, when you put it together in single registration.

I used the following classes and interfaces to test the solution:

class ContactService<TParent> : IContactService<TParent>, IFeatureProvider<ContactFeature<TParent>>
    where TParent : class, IDomainModel
{
}

interface IContactService<T> where T : class, IDomainModel
{
}

interface IFeatureProvider<TProvider>
{
}

interface IDomainModel
{
}

class Household : IDomainModel
{
}

class ContactFeature<TDomainModel> where TDomainModel: class, IDomainModel
{
}

Upvotes: 2

Related Questions