Generic Decorator Chain Resolution Problem

May 6, 2008 at 8:52 PM
I saw <a href="http://www.codeplex.com/unity/Thread/View.aspx?ThreadId=24015">this</a> post a while back about decorator chains and I have tried to follow it to implement similar functionality around some generic classes but am receiving an error when trying to load the configuration for the container.

Example:

public interface IRepository<T, TID>
{
T Get(TID id);
}

public class RepositoryBase<T, TID> : IRepository<T, TID>
{
public T Get(TID id);
}
// do some stuff
}
}

public class LoggingRepositoryDecorator<T, TID> : IRepository<T, TID>
{
private IRepository<T, TID> _inner;

public LoggingRepository(IRepository<T,TID> inner)
{
_inner = inner;
}

public T Get(TID)
{
Debug.Writeline("Get Called");
return _inner.Get(TID);
}
}

My config file looks like this:

<unity>
<typeAliases>
</typeAliases>

<containers>
<container name="baseContainer">
<types>
<!-- Type mapping with no lifetime – defaults to "transient" -->
<type type="Core.Interfaces.IRepository`2, Core.Interfaces" mapTo="Core.Model.LoggingRepositoryDecorator`2, Core.Model" name="logging-decorator">
<typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement,
Microsoft.Practices.Unity.Configuration">
<constructor>
<param name="inner" parameterType="Core.Interfaces.IRepository`2, Core.Interfaces">
<dependency name="generic-repository"/>
</param>
</constructor>
</typeConfig>
</type>
<type type="Core.Interfaces.IRepository`2, Core.Interfaces" mapTo="Core.Model.RepositoryBase`2, Core.Model" name="generic-repository"/>
</types>
<instances>
</instances>
</container>
</containers>
</unity>

Now whenever I try to configure the container I get the following error message:

"The type Core.Model.LoggingRepositoryDecorator`2 does not have a constructor that takes the parameters (IRepository`2)."

It appears from stepping through the source that this is failing on line 68 of InjectionConstructor.cs

62 private ConstructorInfo FindConstructor(Type typeToCreate)
63 {
64 Type[] parameterTypes = Sequence.ToArray(
65 Sequence.Map<InjectionParameterValue, Type>(parameterValues,
66 delegate(InjectionParameterValue parameter) { return parameter.ParameterType; }));
67
68 ConstructorInfo ctor = typeToCreate.GetConstructor(parameterTypes);

I didn't see anything blatantly wrong with the values of this function when it was called, but the GetConstructor is returning a null instance.

From the code above, the LoggingRepositoryDecorator does clearly have a constructor that takes an IRepository`2 parameter named "inner", so I don't know why this is failing. Is this some kind of limitation on the unity container itself, or something I am doing wrong in the configuration or otherwise?
May 8, 2008 at 11:04 PM
Sorry it took so long to get back to you, busy, busy busy.

I've repro'ed the issue; I'll let you know when I have more information.
May 9, 2008 at 5:56 PM
When specifying the constructor parameters, I had use the fully qualified type name for generics. So instead of:

<constructor>
    <param name="inner" parameterType="Core.Interfaces.IRepository`1, Core.Interfaces">
        <dependency name="generic-repository"/>
    </param>
</constructor>
I had to use:

<constructor>
    <param name="inner" parameterType="Core.Interfaces.IRepository`1[[GenParamType, GenParamTypeAssembly]], Core.Interfaces">
        <dependency name="generic-repository"/>
    </param>
</constructor>

I don't know if this is by design, but it should at least enable you to move forward. I haven't tested your example, I'm just going off of memory which is pretty shot these days...
May 13, 2008 at 12:52 PM
mvalenty,

I think you are missing the point here. Having to specify the generic type parameters in the config file defeats the whole purpose of what I'm trying to accomplish. I want to be able to do a Container.Resolve<IRepository<Order, string>>() or a Container.Resolve<IRepository<Customer, string>>() or any other combination of generic type parameters and have the container figure out how to build up the proper decorator chain that includes logging and whatever other repository decorators I may have registered.

Windsor does this very easily, and I believe the intent was for Unity to support this, there just appears to possibly be a bug in the code somewhere.
May 19, 2008 at 12:53 PM
Chris,

I know you are busy, but has there been any progress on this issue?

Thanks,
May 19, 2008 at 8:09 PM


swalkertdci wrote:
Chris,

I know you are busy, but has there been any progress on this issue?

Thanks,


I have absolutely confirmed that it's a bug. Unfortunately, I don't have a fix yet. The fundamental issue is that when you call GetConstructor on a generic type, if the constructor you're asking for has any generic parameters it will not be returned.

The workaround is to call GetConstructors and go through all of them, looking for the right one. Unfortunately, the "right" one is a lot harder to define than you might think in the presence of generics and inheritance. So I'm still working out what the correct logic needs to be.

-Chris

Jun 25, 2008 at 6:04 PM
It's been over a month since your last update.  Has this been fixed yet?

ctavares wrote:


swalkertdci wrote:
Chris,

I know you are busy, but has there been any progress on this issue?

Thanks,


I have absolutely confirmed that it's a bug. Unfortunately, I don't have a fix yet. The fundamental issue is that when you call GetConstructor on a generic type, if the constructor you're asking for has any generic parameters it will not be returned.

The workaround is to call GetConstructors and go through all of them, looking for the right one. Unfortunately, the "right" one is a lot harder to define than you might think in the presence of generics and inheritance. So I'm still working out what the correct logic needs to be.

-Chris




Jun 25, 2008 at 8:07 PM
Funny you should ask. I got a preliminary solution working last night. It's a bit ugly, and requires some core changes, so it's not just a simple extension unfortunately.

I'm going to clean it up an bit and hopefully can release it as a source patch before rolling it into the next full release.

Jul 18, 2008 at 1:11 PM
 I was just trying v1.1 against my existing test project for this bug and it was still giving me the same error.

Did this bug fix make it into the v1.1 release, or am I just doing something stupid?
Jul 18, 2008 at 4:37 PM
No, the bug fix isn't in the 1.1 release. It's in our current (internal) bits. I'm waiting on a bunch of people to get back from vacation to figure out how to release the patch, or if we just wait for v1.2.

Aug 19, 2008 at 12:15 AM
The code with the bug fix in has just been posted in Codeplex source control. Please check it out, bang on it, and let us know. This is a bi-weekly drop, so no promises (yet) on quality.

Nov 9, 2009 at 5:43 AM
Edited Nov 11, 2009 at 2:36 AM

I think I have a similar situation with the following codes

public class PositionRepository : ICheckValue<int,IPosition>

{    ...     }

 

My config looks like the following:

                     <type name="positionRepository"
                          type ="Abstraction.ICheckValue, Abstraction"
                          mapTo="Implementation1.PositionRepository, Implementation1">
                        <typeConfig>
                            <constructor>
                                <param name="idType" parameterTypeName="TId">
                                    <dependency/>
                                </param>
                                <param name="entityType" parameterTypeName="TEntity">
                                    <dependency/>
                                </param>
                            </constructor>
                        </typeConfig>
                    </type>

And I keep on getting this exception message:

The configuration file must include a value for one of "parameterType" and "genericParameterName" for the property or parameter named "idType".

What exactly is going on here and why does it insist on a value for the "idType"?

Jan 12, 2010 at 6:49 AM

No, you just have a syntax error. The error message is:

"The configuration file must include a value for one of "parameterType" and "genericParameterName" for the property or parameter named "idType"."

 

But looking at your config file, you've got:

<param name="idType" parameterTypeName="TId">

parameterTypeName isn't a valid attribute for the <param> tag, it needs to be either parameterType or genericParameterName.