2014年12月23日 星期二

IoC using Unity 3.5 with Generic type

.NET   IOC    Design Pattern   Unity

背景

Unity Application Block (Unity)在泛型的使用方式。
以下分別介紹在Run-time(程式碼注入)以及Design-time(由設定檔注入)的方式。

環境

l   Windows 7 pro
l   IIS Express 7.5
l   Visual Studio 2013 update4 (C#)

實作


Create Model as “parameter” in IOC constructor.

l   A class which will be used as a parameter.
public class Customer
{
        public String Name { get; set; }
        public String Sex { get; set; }
        public String PhoneNumber { get; set; }
}

l   A enum class which will be used as a parameter.
public enum Service
{
        [EnumAttr(Desc = "打蠟")]
        Wax,

        [EnumAttr(Desc = "洗車")]
        Wash,

        [EnumAttr(Desc = "內裝清理")]
        Clean
}



Interface

public interface IPainter<T>
{
   void Paint(T car);
}



Class

l   RedPainter : No constructor
public class RedPainter<T> : IPainter<T> where T: ICar
    {

        public void Paint(T car)
        {
            car.Color = Color.Red;
            Debug.WriteLine("已烤漆為紅色!");
        }
    }


l   BluePainter : Constructor with String parameter
public class BluePainter<T> : IPainter<T> where T: ICar
    {
        private String _extraMsg = String.Empty;
        public BluePainter(String extraMsg)
        {
            this._extraMsg = extraMsg;
        }
        public void Paint(T car)
        {
            car.Color = Color.Blue;
            Debug.WriteLine(String.Format("已烤漆為藍色! ({0}) ", this._extraMsg));

        }
    }


l   SilverPainter : Constructor with Enum parameter
public class SilverPainter<T> : IPainter<T> where T: ICar
    {
        private Service _service;
        public SilverPainter(Service service)
        {
            this._service = service;
        }
        public void Paint(T car)
        {
            car.Color = Color.Blue;
            Debug.WriteLine(String.Format("已烤漆為銀色! ({0}) ",
                String.Concat("額外服務:",this._service.GetAttr().Desc)));
        }
    }


l   BlackPainter : Constructor with Json parameter (Deserize to an object later)
public class BlackPainter<T> : IPainter<T> where T: ICar
    {
        private Customer _customer;
        public BlackPainter(String jsonStr)
        {
            //Deserialize
            using (var deserializer = new Adaman.Infra.Serializer.JsonSerializer())
            {
                this._customer = deserializer.DeserializeToObject<Customer>(jsonStr);
            }
        }
        public void Paint(T car)
        {
            car.Color = Color.Blue;
            Debug.WriteLine(String.Format("已烤漆為黑色! ({0},{1},{2}) ",
                this._customer.Name,
                this._customer.Sex,
                this._customer.PhoneNumber));
        }
    }



Run-time DI

IPainter<ICar> painter = null;

IUnityContainer container = new UnityContainer();

//注入容器
container.RegisterType<IPainter<ICar>, BluePainter<ICar>>(
                new InjectionConstructor("金額 10,000"));

//取得容器的组件
painter = container.Resolve<IPainter<ICar>>();

//使用注入的物件
ICar focus = new Focus();
painter.Paint(focus);

//GC
container.Dispose();

執行結果 :
已烤漆為藍色! (金額 10,000)



Create Model as “parameter” in IOC constructor.

l   A class which will be used as a parameter.
public class Customer
{
        public String Name { get; set; }
        public String Sex { get; set; }
        public String PhoneNumber { get; set; }
}

l   A enum class which will be used as a parameter.
public enum Service
{
        [EnumAttr(Desc = "打蠟")]
        Wax,

        [EnumAttr(Desc = "洗車")]
        Wash,

        [EnumAttr(Desc = "內裝清理")]
        Clean
}



Interface

public interface IPainter<T>
{
   void Paint(T car);
}



Class

l   RedPainter : No constructor
public class RedPainter<T> : IPainter<T> where T: ICar
    {

        public void Paint(T car)
        {
            car.Color = Color.Red;
            Debug.WriteLine("已烤漆為紅色!");
        }
    }


l   BluePainter : Constructor with String parameter
public class BluePainter<T> : IPainter<T> where T: ICar
    {
        private String _extraMsg = String.Empty;
        public BluePainter(String extraMsg)
        {
            this._extraMsg = extraMsg;
        }
        public void Paint(T car)
        {
            car.Color = Color.Blue;
            Debug.WriteLine(String.Format("已烤漆為藍色! ({0}) ", this._extraMsg));

        }
    }


l   SilverPainter : Constructor with Enum parameter
public class SilverPainter<T> : IPainter<T> where T: ICar
    {
        private Service _service;
        public SilverPainter(Service service)
        {
            this._service = service;
        }
        public void Paint(T car)
        {
            car.Color = Color.Blue;
            Debug.WriteLine(String.Format("已烤漆為銀色! ({0}) ",
                String.Concat("額外服務:",this._service.GetAttr().Desc)));
        }
    }


l   BlackPainter : Constructor with Json parameter (Deserize to an object later)
public class BlackPainter<T> : IPainter<T> where T: ICar
    {
        private Customer _customer;
        public BlackPainter(String jsonStr)
        {
            //Deserialize
            using (var deserializer = new Adaman.Infra.Serializer.JsonSerializer())
            {
                this._customer = deserializer.DeserializeToObject<Customer>(jsonStr);
            }
        }
        public void Paint(T car)
        {
            car.Color = Color.Blue;
            Debug.WriteLine(String.Format("已烤漆為黑色! ({0},{1},{2}) ",
                this._customer.Name,
                this._customer.Sex,
                this._customer.PhoneNumber));
        }
    }



Run-time DI

IPainter<ICar> painter = null;

IUnityContainer container = new UnityContainer();

//注入容器
container.RegisterType<IPainter<ICar>, BluePainter<ICar>>(
                new InjectionConstructor("金額 10,000"));

//取得容器的组件
painter = container.Resolve<IPainter<ICar>>();

//使用注入的物件
ICar focus = new Focus();
painter.Paint(focus);

//GC
container.Dispose();

執行結果 :
已烤漆為藍色! (金額 10,000)



Design-time DI

l   主程式
using Microsoft.Practices.Unity.Configuration; // Must for LoadConfiguration method!

IPainter<ICar> painter = null;

IUnityContainer container = new UnityContainer();

//Config注入容器
container.LoadConfiguration("UnityCfg_Painter");
//取得IFile容器的组件
painter = container.Resolve<IPainter<ICar>>();

ICar focus = new Focus();
painter.Paint(focus);

//GC
container.Dispose();



l   設定檔 : 注入 RedPainter : No constructor

在設定Config前,必須知道下面三個型別的完整型別名稱:
1.  Generic interface
2.  Generic class (which implement 1.)
3.  Generic Data type

在此範例對應為:
1.  IPainter<>
2.  RedPainter<>
3.  ICar


只要使用以下程式碼即可知道完整型別名稱
Debug.WriteLine(typeof(IPainter<>).AssemblyQualifiedName);

Output :

DI_Generic.Strategy.IPainter`1, DI_Generic, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

接著便可以開始設定Config
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
  </configSections>
   
  <unity>
    <assembly name="DPLib" />
    <assembly name="DI_Generic" />
    <namespace name="DPLib" />
    <namespace name="DI_Generic.Strategy" />

    <alias alias="CarInterface" type="DPLib.ICar, DPLib" />
    <alias alias="PainterInterface" type="DI_Generic.Strategy.IPainter`1, DI_Generic" />
    <alias alias="PainterClass" type="DI_Generic.Strategy.RedPainter`1, DI_Generic" />

    <container name="UnityCfg_Painter">
      <register type="PainterInterface[CarInterface]"
mapTo="PainterClass[CarInterface]">
      </register>
    </container>
   
  </unity>
 </configuration>

PS. 注意在Register type時的寫法,必須用”[]”包住Generic Data type
<register type="PainterInterface[CarInterface]"
mapTo="PainterClass[CarInterface]" />

執行結果:
已烤漆為紅色!


l   設定檔 : 注入 BluePainter : Constructor with String parameter

<Register> tag裡加入 <constructor> & <param>注意名稱必須和實際的參數名稱相同

<configuration>
  <unity>
    <alias alias="CarInterface" type="DPLib.ICar, DPLib" />
    <alias alias="PainterInterface" type="DI_Generic.Strategy.IPainter`1, DI_Generic" />
    <alias alias="PainterClass" type="DI_Generic.Strategy.BluePainter`1, DI_Generic" />

    <container name="UnityCfg_Painter">
      <register type="PainterInterface[CarInterface]"
mapTo="PainterClass[CarInterface]">
<constructor>
          <param name="extraMsg" value="客戶下午兩點取車"/>
        </constructor>
      </register>
    </container>
   
  </unity>
 </configuration>

執行結果:
已烤漆為藍色! (客戶下午兩點取車)



l   設定檔 : 注入 SilverPainter : Constructor with Enum parameter

參數為列舉型別時,必須手動指定參數的型別,而value的設定方式:
Ex1. value="Wax" => Service.Wax
Ex2. value="Wash" => Service.Wash
Ex3. value="Clean" => Service.Clean

<configuration>
  <unity>
    <alias alias="CarInterface" type="DPLib.ICar, DPLib" />
    <alias alias="PainterInterface" type="DI_Generic.Strategy.IPainter`1, DI_Generic" />
    <alias alias="PainterClass" type="DI_Generic.Strategy.SilverPainter`1, DI_Generic" />

    <container name="UnityCfg_Painter">
      <register type="PainterInterface[CarInterface]"
mapTo="PainterClass[CarInterface]">
<constructor>
          <param name="service" value="Wax" type="DI_Generic.Service, DI_Generic"/> 
        </constructor>
      </register>
    </container>
   
  </unity>
 </configuration>

執行結果:
已烤漆為銀色! (額外服務:打蠟)




l   設定檔 : 注入 BlackPainter :
Constructor with Json parameter (Deserize to an object later)

如果注入的class的建構參數為一物件,折衷的方式是帶入JSON,再deserialize成物件。
但是JSON中的雙引號在放進Config時,必須改成HTML字元:&quot;

<configuration>
  <unity>
    <alias alias="CarInterface" type="DPLib.ICar, DPLib" />
    <alias alias="PainterInterface" type="DI_Generic.Strategy.IPainter`1, DI_Generic" />
    <alias alias="PainterClass" type="DI_Generic.Strategy.BlackPainter`1, DI_Generic" />

    <container name="UnityCfg_Painter">
      <register type="PainterInterface[CarInterface]"
mapTo="PainterClass[CarInterface]">
<constructor>
          <param name="jsonStr" value="{&quot;Name&quot;: &quot;JB&quot;,&quot;Sex&quot;: &quot;&quot;,&quot;PhoneNumber&quot;: &quot;0933123XXX&quot;}" />
        </constructor>
      </register>
    </container>
   
  </unity>
 </configuration>

執行結果:
已烤漆為黑色! (JB,,0933123XXX)




Conclusion


在寫IOC程式碼時,一開始會花比較多時間,但是完成後,對於程式的彈性和可擴展性都加了不少分。



Reference