質問

I have an application that uses unity FW to resolve objects throughout. I have done some change to the framework and the classes which can be seen in the code comment as "NEW CHANGE"

The wrapper class looks like

public static class ContractResolver
{
        public static T Resolve<T>() //This is been used in many places in application
        {
            IUnityContainer container = new UnityContainer();
            var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
            section.Containers.Default.Configure(container);
            return container.Resolve<T>();
        }

        //NEW CHANGE: This is the new function that suppose to return the instance of parameterised constructor
        public static T Resolve<T>(ParameterOverride[] parameterOverrides)
        {
            IUnityContainer container = new UnityContainer();
            var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
            section.Containers.Default.Configure(container);
            return container.Resolve<T>(parameterOverrides);
        }
}

The configuration looks like

  <unity>
    <containers>
      <container>
        <types>
          <type type ="UnityTest.IImageRepositoryService, UnityTest" mapTo="UnityTest.ImageRepositoryService, UnityTest"/>
        </types>
      </container>
    </containers>
  </unity>

The classes and interface looks like

public interface IImageRepositoryService
{
    bool Exists(string imageName);
}

public class ImageRepositoryService : IImageRepositoryService
{
    private readonly string mFilterName = "StandardImageFilter";

    //[InjectionConstructor]
    public ImageRepositoryService()
    {
        DatabaseQueryProvider.Query("Image", mFilterName);
    }

    //NEW CHANGE. A CONSTRUCTOR THAT ACCEPTS A PARAMETER    
    //[InjectionConstructor]
    public ImageRepositoryService(string filterName)
    {
        mFilterName = filterName;
        DatabaseQueryProvider.Query("Image", filterName);
    }

    public bool Exists(string imageName)
    {
        Console.WriteLine("The image " + imageName + " found in filter " + mFilterName);
        return true;
    }

}

The usage looks like

var serviceDefault = ContractResolver.Resolve<IImageRepositoryService>();
serviceDefault.Exists("myimage.bmp");

The new changes breaks the old usage. i.e.

var serviceDefault = ContractResolver.Resolve<IImageRepositoryService>();

Throws exception Resolution of the dependency failed, type = "UnityTest.IImageRepositoryService", name = "(none)". Exception occurred while: while resolving. Exception is: InvalidOperationException - The type String cannot be constructed. You must configure the container to supply this value.

I would like to have the new functionality at the same time do not want to break the old functionality.

 var serviceDefault = ContractResolver.Resolve<IImageRepositoryService>();
 serviceDefault.Exists("myimage.bmp");

Should display the message in the console "The image myimage.bmp found in filter StandardImageFilter"

var parameterOverride1 = new ParameterOverride("filterName", "filter1");
var servicefilter1 = ContractResolver.Resolve<IImageRepositoryService>(new[] { parameterOverride1 });
servicefilter1.Exists("myimage.bmp");

Should display the message in the console "The image myimage.bmp found in filter filter1"

var parameterOverride2 = new ParameterOverride("filterName", "filter2");
var servicefilter2 = ContractResolver.Resolve<IImageRepositoryService>(new[] { parameterOverride2 });
servicefilter2.Exists("myimage.bmp");

Should display the message in the console "The image myimage.bmp found in filter filter2"

How solve this problem?

役に立ちましたか?

解決

If you want to resolve the same type (in this case IImageRepositoryService) but have different calls to Resolve invoke different constructors then you will need to use named registrations.

In your case you can do this in the XML configuration:

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  <container>
    <register type ="UnityTest.IImageRepositoryService, UnityTest" mapTo="UnityTest.ImageRepositoryService, UnityTest">
      <constructor />
    </register>
    <register name="ParameterizedRepository" 
              type="UnityTest.IImageRepositoryService, UnityTest" 
              mapTo="UnityTest.ImageRepositoryService, UnityTest">
      <constructor>
        <param name="filterName" value="dummyValue" />
      </constructor>
    </register>
  </container>
</unity>

Note, that I've used the Unity 2 (and 3) configuration style.

So this tells Unity that when resolving using the name "ParameterizedRepository" to invoke the constructor with the parameter named "filterName". I've used a dummy value here because we are going to override the value at runtime anyway:

var imageRepositoryService = container.Resolve<IImageRepositoryService>(
    "ParameterizedRepository", 
    new ParameterOverride("filterName", "filter2"));

So that's how to get what you want using Unity so in terms of your wrapper class you should add a name parameter:

public static class ContractResolver
{
    //NEW CHANGE: This is the new function that suppose to return the instance of parameterised constructor
    public static T Resolve<T>(string name, params ParameterOverride[] parameterOverrides)
    {
        IUnityContainer container = new UnityContainer();
        var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
        section.Containers.Default.Configure(container);
        return container.Resolve<T>(name, parameterOverrides);
    }
}

A few unsolicited comments (in the spirit of trying to be helpful):

  • It looks like you are using Unity version 1. If so, you might want to consider upgrading (version 3 was released recently) and if you are not using Unity version 1, then you might want to consider changing the XML configuration syntax to use the newer approach as well as using the LoadConfiguration() extension method.

  • I'm not sure why every call to ContractResolver.Resolve() creates a new Unity container and then loads the configuration. This could be a bit of a performance hit. Usually, you would create a container and load the configuration once and use that instance for the lifetime of the application.

  • I can understand how you would be tempted to hide the container implementation behind the ContractResolver but with the addition of ParameterOverride (which are Unity specific) the abstraction is becoming a bit leaky.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top