In the previous article of the series, I talked about constructor and property (setter) injection. I wanted to write about how to work with arrays and generics in Unity in this blog, after seeing how lengthy this one got, I’ve decided to write about generics in the next one. This one will only concentrate on arrays. My Product4 class has the following definition: 1: public interface IProduct
2: {
3: string WriteProductDetails();
4: }
5:
6: public class Product4 : IProduct
7: {
8: public string Name { get; set; }
9: public ILogger[] Loggers { get; set; }
10:
11: public Product4(string productName, ILogger[] loggers)
12: {
13: Name = productName;
14: Loggers = loggers;
15: }
16:
17: public string WriteProductDetails()
18: {
19: StringBuilder productDetails = new StringBuilder();
20: productDetails.AppendFormat("{0}<br/>", Name);
21: for (int i = 0; i < Loggers.Count(); i++)
22: {
23: productDetails.AppendFormat("{0}<br/>", Loggers[i].WriteLog());
24: }
25:
26: return productDetails.ToString();
27: }
28: }
The key parts are line 4 where we declare an array of ILogger and line 5 where-in the constructor passes an instance of an array of ILogger objects.
I’ve created another class – FakeLogger:
1: public class FakeLogger : ILogger
2: {
3: public string WriteLog()
4: {
5: return string.Format("Type: {0}", GetType());
6: }
7: }
It’s implementation is the same as what we had for the FileLogger class. Coming to the web.config file, first add the following aliases. The alias for FakeLogger should make sense right away. ILoggerArray defines an array of ILogger objects. I’ll tell why we need an alias for System.String data type.
1: <typeAlias alias="string" type="System.String, mscorlib" />
2: <typeAlias alias="ILoggerArray" type="ProductModel.ILogger[], ProductModel" />
3: <typeAlias alias="FakeLogger" type="ProductModel.FakeLogger, ProductModel"/>
Next is to create mappings for the FileLogger and FakeLogger classes:
1: <type type="ILogger" mapTo="FileLogger" name="logger1">
2: <lifetime type="singleton" />
3: </type>
4: <type type="ILogger" mapTo="FakeLogger" name="logger2">
5: <lifetime type="singleton" />
6: </type>
Finally, for the real deal:
1: <type type="IProduct" mapTo="Product4" name="ArrayProduct">
2: <typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement,Microsoft.Practices.Unity.Configuration, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
3: <constructor>
4: <param name="productName" parameterType="string" >
5: <value value="Product name from config file" type="string"/>
6: </param>
7: <param name="loggers" parameterType="ILoggerArray">
8: <array>
9: <dependency name="logger2" />
10: <dependency name="logger1" />
11: </array>
12: </param>
13: </constructor>
14: </typeConfig>
15: </type>
Here’s where I’m saying, that if a type of IProduct is requested to be resolved, map it to type Product4. Furthermore, the Product4 has two constructor parameters – a string and an array of type ILogger. You might have observed the first parameter of the constructor is named ‘productName’ and that matches the value in the name attribute of the param element. The parameterType of ‘string’ maps to ‘System.String, mscorlib’ and is defined in the type alias above.
The set up is similar for the second constructor parameter. The name matches the name of the parameter (loggers) and is of type ILoggerArray, which maps to an array of ILogger objects. We’ve also decided to add two elements to this array when unity resolves it – an instance of FileLogger and one of FakeLogger.
The click event of the button does the following:
1: //unityContainer.RegisterType<IProduct, Product4>();
2: //IProduct product4 = unityContainer.Resolve<IProduct>();
3: IProduct product4 = unityContainer.Resolve<IProduct>("ArrayConstructor");
4: productDetailsLabel.Text = product4.WriteProductDetails();
It’s worth mentioning here about the change in the format of resolving the IProduct to create an instance of Product4. You cannot use the regular way (the commented lines) to get an instance of Product4. The reason is due to the behavior of Unity which Alex Ermakov has brilliantly explained here.
The corresponding output of the action is:
You have a couple of options when it comes to adding dependency elements in the array node. You can:
- leave it empty (no dependency elements declared): This will only create an empty array of loggers. This way you can check for non-null condition, in your mock classes.
- add multiple dependency elements with the same name
1: <param name="loggers" parameterType="ILoggerArray">
2: <array>
3: <dependency name="logger2" />
4: <dependency name="logger2" />
5: </array>
6: </param>
With this you’ll see two instances of FakeLogger in the output. This article shows how Unity allows you to instantiate objects with arrays. Find the code here.