Injecting pairs of values into the constructor

Jun 22, 2012 at 8:43 PM

Hi all - I'm having an interesting problem with Unity, and any help is appreciated.


I want to inject an array of objects in a constructor, and I want to associate each object with a code during that injection.
Note that there can be more than one object with the same code, so I can't have a Dictionary.

I'm basically trying to inject small objects into a containing object, and then the containing object will use the code to access these objects when necessary.


I would be fine with using a KeyValuePair<string, MyObject>; I would also be find if I had to construct a composite object (like below).
What I would rather not do is to seperately register all of the combinations of Code/Object elements before-hand.  I don't mind registering each of the injected objects.

I'd like to do something like this (I know it's not valid):

<register type="IMyObject" mapTo="MySmallObject1" name="SmallObject1" />
<register type="IMyObject" mapTo="MySmallObject2" name="SmallObject2" />
<register type="IMyObject" mapTo="MySmallObject3" name="SmallObject3" />



<register type="ContainingObject1" name="Name1">
  <constructor>
    <param name="objects">
      <array>
        <value>
          <!-- This could resolve to a KeyValuePair or a composite object -->
          <property name="Code" value = "01" />
          <property name="Operation" >
            <dependency name="SmallObject1" />
          </property>
        </value>
        <value>
          <property name="Code" value = "02" />
          <property name="Operation" >
            <dependency name="SmallObject2" />
          </property>
        </value>
      </array>
    </param>
  </constructor>
</register>

<register type="ContainingObject2" name="Name2">
  <constructor>
    <param name="objects">
      <array>
        <value>
          <property name="Code" value = "01" />
          <property name="Operation" >
            <dependency name="SmallObject3" />
          </property>
        </value>
      </array>
    </param>
  </constructor>
</register>
So in the first contained object, I'm injecting two pairs: code 01 with SmallObject1, and code 02 with SmallObject2.
For the second, there's only one pair: code 01 with SmallObject3.

I know that I could write a custom type converter that could take a string of the form "01,SmallObject1" and convert it to a string and the object by resolving the type name in code.
I was hoping to avoid that, if possible, and control all of this in the config.

Any ideas?

Thanks!
Phil


  

Jun 25, 2012 at 1:43 PM

So I solved it in a way that's similary to the techniques that Rory Primrose has - I created a section extension and then used that to pass my Code/Operation pair.  Since the section extension's GetInjectionParameterValue method takes the unity container, I can resolve the Operation type by name. 

Something like:

 

    public class OperationSectionExtension : Microsoft.Practices.Unity.Configuration.SectionExtension
    {
        public override void AddExtensions(Microsoft.Practices.Unity.Configuration.SectionExtensionContext context)
        {
            context.AddElement<OperationParameterValueElement>(OperationParameterValueElement.ElementName);
        }
    }

    public class OperationParameterValueElement : Microsoft.Practices.Unity.Configuration.ParameterValueElement
    {
        public const string ElementName = "operation";

        public override InjectionParameterValue GetInjectionParameterValue(IUnityContainer container, Type parameterType)
        {
            if (!container.IsRegistered<IOperation>(Operation))
            {
                throw new System.Configuration.ConfigurationErrorsException(string.Format("The type {0} is not registered. Operations must be listed in the Unity configuration before they are used in any operations mapping.", Operation));
            }
            ContainerRegistration operationRegistration = 
                container.Registrations.FirstOrDefault(r => Operation.Equals(r.Name, StringComparison.InvariantCulture));

            if (operationRegistration == null)
            {
                throw new System.Configuration.ConfigurationErrorsException(string.Format("Cannot find type registration for {0}", Operation));
            }

            if (operationRegistration.MappedToType == null)
            {
                throw new System.Configuration.ConfigurationErrorsException(string.Format("Type {0} is not mapped to any valid type", Operation));
            }

            var operation = container.Resolve(operationRegistration.MappedToType) as IOperation;
            if (operation == null)
            {
                throw new System.Configuration.ConfigurationErrorsException(string.Format("Type {0} cannot be converted to Operation", Operation));
            }
            return new InjectionParameter(parameterType, new KeyValuePair<string, IOperation>(Code, operation));
        }

        [System.Configuration.ConfigurationProperty("code", IsRequired = true)]
        public string Code
        {
            get { return (string)base["code"]; }
            set { base["code"] = value; }
        }

        [System.Configuration.ConfigurationProperty("operation", IsRequired = true)]
        public string Operation
        {
            get { return (string)base["operation"]; }
            set { base["operation"] = value; }
        }
    }
This builds up a KeyValuePair that has the Code and Operation.  Now, in my config, I can do this:
 
<register type="IMyObject" mapTo="MySmallObject1" name="SmallObject1" />
<register type="IMyObject" mapTo="MySmallObject2" name="SmallObject2" />
<register type="IMyObject" mapTo="MySmallObject3" name="SmallObject3" />



<register type="ContainingObject1" name="Name1">
  <constructor>
    <param name="objects">
      <array>
        <operation code="01" operation="SmallObject1" /> 
        <operation code="02" operation="SmallObject12 /> 
      </array> 
    </param> 
  </constructor> 
</register> 
<register type="ContainingObject2" name="Name2"> 
  <constructor>
    <param name="objects"> 
      <array> 
        <operation code="01" operation="SmallObject3" /> 
      </array> 
    </param> 
  </constructor> 
</register> 
The array of KeyValuPairs is passed to the constructor, which is just what I want.
Of course, I have to put the <sectionExtension> tag for my section extension outside of the container.
Thanks,
Phil