Showing posts with label IoC container. Show all posts
Showing posts with label IoC container. Show all posts

2016-12-26

Castle Windsor Part 2 - Array configuration, dictionary configuration

Xem original

Part 2 – Array Configuration
Part 3 – Dictionary configuration

Array configuration

Đầu tiên là config array.

Ví dụ holiday service có code như sau:
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using System;

namespace ConsoleApp
{
    public class HolidayService
    {
        private DateTime[] holidays;

        public DateTime[] Holidays
        {
            get { return holidays; }
            set { holidays = value; }
        }

        public bool IsHoliday(DateTime date)
        {
            if (holidays != null)
            {
                DateTime matchDate = date.Date;
                foreach (DateTime dt in Holidays)
                {
                    if (dt.Date.Equals(matchDate))
                    {
                        return true;
                    }
                }
            }

            return false;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            WindsorContainer container = new WindsorContainer(new XmlInterpreter());

            HolidayService holidayService = container.Resolve<HolidayService>();

            DateTime xmas = new DateTime(2016, 12, 25);
            DateTime newYears = new DateTime(2017, 1, 1);

            if (holidayService.IsHoliday(xmas))
            {
                Console.WriteLine("Merry X'mas!");
            }
            else
            {
                Console.WriteLine("X'mas is only for management!");
            }

            if (holidayService.IsHoliday(newYears))
            {
                Console.WriteLine("Happy new year!");
            }
            else
            {
                Console.WriteLine("New year, you haven't done all the work for last year!");
            }

            Console.ReadLine();
        }
    }
}
Config trong App.config như sau:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component type="ConsoleApp.HolidayService, ConsoleApp">
        <parameters>
          <holidays>
            <array>
              <item>2016-12-24</item>
              <item>2016-12-25</item>
              <item>2017-1-1</item>
            </array>
          </holidays>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Có thể dùng <Holidays> hoặc <holidays> đều được vì Windsor đủ smart để inject. Nếu muốn resolve trực tiếp ra IList (hoặc IList, IEnumerable ... nói chung là generic collection)
static void Main(string[] args)
{
    WindsorContainer container = new WindsorContainer(new XmlInterpreter());
    var holidays = container.Resolve<IList<DateTime>>("holidays");
    Console.WriteLine(string.Join("\r\n", holidays));
    Console.ReadLine();
}
File config tương ứng như sau:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="holidays" type="System.Collections.Generic.List`1[System.DateTime]">
        <parameters>
          <collection>
            <array>
              <item>2016-12-24</item>
              <item>2016-12-25</item>
              <item>2017-1-1</item>
            </array>
          </collection>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>

Cần nhớ ở dạng config này là thực hiện theo <parameters><collection><array><item>. Nếu đầy đủ hơn thì type chỉ định có cả assembly là <component id="holidays" type="System.Collections.Generic.List`1[[System.DateTime, mscorlib]], mscorlib">.

Dictionary configuration

Tương tự như array, dictionary configuration thực hiện với <parameters><dictionary><dictionary><entry>. Ví dụ AliasService như sau:
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using System;
using System.Collections.Generic;

namespace ConsoleApp
{
    public class AliasService
    {
        private Dictionary<string, string> dict;

        public Dictionary<string, string> Aliases
        {
            get { return dict; }
            set { dict = value; }
        }

        public string Evaluate(string term)
        {
            if (dict == null)
            {
                return term;
            }

            while (dict.ContainsKey(term))
            {
                term = dict[term];
            }

            return term;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            WindsorContainer container = new WindsorContainer(new XmlInterpreter());

            AliasService aliasService = container.Resolve<AliasService>();
            string sentence = "A dog ate my homework";

            foreach (string word in sentence.Split(new char[] { ' ' }, 
                StringSplitOptions.RemoveEmptyEntries))
            {
                Console.Write("{0} ", aliasService.Evaluate(word));
            }

            Console.ReadLine();
        }
    }
}
App.config cấu hình dictionary:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component type="ConsoleApp.AliasService, ConsoleApp">
        <parameters>
          <Aliases>
            <dictionary>
              <entry key="dog">duck</entry>
              <entry key="ate">broke</entry>
              <entry key="homework">code</entry>
            </dictionary>
          </Aliases>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Để resolve trực tiếp ra IDictionary
static void Main(string[] args)
{
    WindsorContainer container = new WindsorContainer(new XmlInterpreter());
    var states = container.Resolve<IDictionary<string, string>>("states");
    Console.WriteLine(string.Join("\r\n", states.Keys));
    Console.ReadLine();
}
File config tương ứng như sau:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="states" type="System.Collections.Generic.Dictionary`2[System.String, System.String]">
        <parameters>
          <dictionary>
            <dictionary>
              <entry key="VN-CT">Cần Thơ</entry>
              <entry key="VN-DN">Đà Nẵng</entry>
              <entry key="VN-HN">Hà Nội</entry>
              <entry key="VN-HP">Hải Phòng</entry>
              <entry key="VN-SG">Hồ Chí Minh</entry>
            </dictionary>
          </dictionary>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Tới đây là xong part 2. Về type convertor có thể tham khảo thêm phần Configuration with type converters trong series của Mike Hadlow '10 Advanced Windsor tricks'.

Container tutorials ... example with Castle Windsor. Part 1 - Configuration parameters.

Mình dùng Castle Windsor từ lâu. Cũng có note nhưng rồi để đâu mất. Mình đã tính viết 1 series làm sao đủ để xài Windsor từ cái thời còn dùng CAB/SCSF, sau đó là Prism nhưng rồi chẳng biết sao lúc đó bỏ nửa chừng.

Inversion of Control (IoC), Dependency Inversion Principle (DIP) và Dependency Injection (DI), CAB&SCSF cũng chỉ viết tới Part 03: Dependency Injection

Gần đây coi lại và thấy lần này nên note kỹ. Nói chung đầu tiên là lược dịch từ những tutorials trên NET bắt đầu từ series trên BitterCoder (đã rất lâu rồi từ tận 2007). Có thể đọc thêm hướng dẫn từ project chính thức trên Github castleproject/Windsor.

Gần đây có nhiều lựa chọn IoC container cho .NET gồm có Autofac, StructureMap, Unity. Một vài container có vẻ dần chiếm được nhiều quan tâm hơn như Autofac với simple API, dễ sử dụng và performance tốt. Tuy nhiên Windsor nói chung vẫn đáp ứng được yêu cầu đặt ra với nhiều module (facilities) từ logging, NHibernate, ASP.NET MVC ...

Configuration parameters

Part đầu tiên là configuration parameters. Windsor cho phép cấu hình component với parameters run-time. Mặc dù có thể configuration với .NET qua app.config bình thường nhưng sử dụng với Windsor khá là đơn giản và tiện dụng.

Tạo một Project ConsoleApp, dùng NuGet install Castle Windsor. Tạo file app.config.


Giả sử có một class là Tax, mặc định tax rate là 10%. Có thể config rate thông qua app.config.
public class Tax
{
    private decimal rate = 0.10m;

    public decimal Rate
    {
        set { rate = value; }
        get { return rate; }
    }

    public decimal Calculate(decimal gross)
    {
        return Math.Round(rate * gross, 2);
    }
}
Sau khi tạo class Tax, thực hiện dùng với Windsor như sau:
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using System;

namespace ConsoleApp
{
    public class Tax
    {
        private decimal rate = 0.10m;

        public decimal Rate
        {
            set { rate = value; }
            get { return rate; }
        }

        public decimal Calculate(decimal gross)
        {
            return Math.Round(rate * gross, 2);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            WindsorContainer container = new WindsorContainer(new XmlInterpreter());

            // Resolve
            Tax calculator = container.Resolve<Tax>();

            decimal gross = 100;
            decimal tax = calculator.Calculate(gross);

            Console.WriteLine("Gross: {0}, Tax: {1}", gross, tax);
            Console.ReadLine();
        }
    }
}
Container được tạo với XmlInterpreter() sẽ đọc configuration từ file app.config (hoặc web.config với web app), instance calculator được tạo bởi container thông qua Resolve().

Thực hiện Build và Run sẽ ra báo lỗi không có config section 'castle'.
Thực hiện thêm config section vào App.config. Giả sử App.config không có cấu hình component sẽ gây exception ComponentNotFoundException
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Thực hiện config component nhưng không set tax rate như sau
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="tax" type="ConsoleApp.Tax, ConsoleApp">
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Type có thể không cần dùng AssemblyQualifiedName mà để đơn giản là Tax hoặc ConsoleApp.Tax cũng OK. Kết quả trả ra 'Gross: 100, Tax: 10.00'.

Thực hiện setup rate bằng parameters như sau:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="tax" type="ConsoleApp.Tax, ConsoleApp">
        <parameters>
          <rate>0.25</rate>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
Kết quả trả ra 'Gross: 100, Tax: 25.00'.

Với id="tax" có thể dùng để resolve component khác nhau, ví dụ:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
  </configSections>
  <castle>
    <components>
      <component id="tax1" type="ConsoleApp.Tax, ConsoleApp">
        <parameters>
          <rate>0.25</rate>
        </parameters>
      </component>
      <component id="tax2" type="ConsoleApp.Tax, ConsoleApp">
        <parameters>
          <rate>0.05</rate>
        </parameters>
      </component>
    </components>
  </castle>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
</configuration>
và code
WindsorContainer container = new WindsorContainer(new XmlInterpreter());
decimal gross = 100;

// Resolve tax #1
Tax calculator1 = container.Resolve<Tax>("tax1");
decimal tax1 = calculator1.Calculate(gross);
Console.WriteLine("Gross: {0}, Tax: {1}", gross, tax1);

// Resolve tax #2
Tax calculator2 = container.Resolve<Tax>("tax2");
decimal tax2 = calculator2.Calculate(gross);
Console.WriteLine("Gross: {0}, Tax: {1}", gross, tax2);

Console.ReadLine();
Như vậy coi như là đủ cho part 1. Phần tiếp theo sẽ thực hiện config arrays, dictionary ...