Showing posts with label Dependency Injection. Show all posts
Showing posts with label Dependency Injection. 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 ...

2010-02-26

Inversion of Control (IoC), Dependency Inversion Principle (DIP) và Dependency Injection (DI)


Inversion of Control (IoC), Dependency Inversion và Dependency Injection

Mục tiêu giới thiệu Inversion of Control (IoC), Dependency Inversion và mối liên quan giữa 2 khái niệm này với Dependency Injection.

Trên Wikipedia và trong series của Rich cũng không define chính xác IoC là gì. Inversion of Control ý muốn đề cập tới các software architecture designs có flow of control ngược với kiến trúc cũ dạng software libraries (traditional architecture of software libraries). Không có gì ghê gớm cả ... :D, cứ cố hiểu đơn giản vậy là được. Trên blog của Martin Flower cũng có một bài viết khá hay 'Inversion of Control Containers and the Dependency Injection pattern', nếu có thời gian thì bạn có thể tham khảo để biết quan điểm của một người chuyên nghiên cứu về software design.

Inversion of Control in Relation to Frameworks

IoC có quan hệ với framework và code re-used. Thông thường khi sử dụng lại code chúng ta thường call một library của ai đó. Ví dụ trong .NET khi gọi method Math.Tan tức là make a call và tức là có control method đó (đơn là hãy nghĩ rằng sử dụng được nó có nghĩ là control được nó).
Nhưng khi thực hiện implement một IComparable hay IEnumerable thì .NET framework sẽ call ngược lại code implement. Trong những trường hợp này thì direction of control bị đảo ngược: something else calling you. Ví dụ implement IComparable thì phải implement method CompareTo. Khi chúng ta gọi hàm Sort thì .NET framework gọi ngược lại method CompareTo mà chúng ta implement.

Inversion of Control và Dependency Injection

Hai khái niệm này thường được coi là đồng nghĩa tuy nhiên DI chỉ là một dạng đặc biệt mô tả cách thực hiện IoC tức IoC có một phạm vi lớn hơn DI.

Inversion of Control và CAB

CAB là một framework IoC và cho phép chúng ta thực hiện DI. Tức CAB có thể thực hiện call us chứ không hoàn toàn là một framework để chúng ta call method và sử dụng. Ví dụ như trong lab về module loader hàm Load() của ModuleInit đã được CAB thực hiện.

Dependency Inversion

Một term có liên quan và dễ gây bối rối là Dependency Inversion. Dependency Inversion còn gọi là Dependency Inversion Principle- DIP, là wider concept của Dependency Injection. DI chỉ là một phần trong DIP, DIP quy định cách thức mà một high-module cần thực hiện khi sử dụng một low-module. Inversion (đảo ngược) ở đây nằm ở chỗ high level thông thường dựa trên low level thì sẽ chuyển lại giở đây cả high level và low level depend upon một shared abstraction. Software consultant Robert C. Martin (tham khảo Agile Software Development, Principles, Patterns, and Practices published by Prentice Hall 10/15/2002 ISBN-10: 0135974445) phát biểu như sau:

High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.

Đại khái là module cao hơn không nên phụ thuộc vào module bên dưới. Cả hai nên dựa vào một 'trừu tượng'. 'Trừu tượng' không được phụ thuộc vào 'chi tiết' mà 'chi tiết' sẽ phụ thuộc 'trừu tượng'.

DIP và IoC

Trong bài lab về DI chúng ta chỉ thực hiện invert dependent giữa các classes. Chúng ta chưa thấy IoC xuất hiện rõ ràng trong ví dụ đó. Nói chung chúng ta chưa cần đi quá sâu thay vì chỉ cần hiểu có thể làm được gì với CAB.

CAB & SCSF - Part 03: Dependency Injection


Dependency Injection

Tìm hiểu Dependency Injection ở phạm vi chung không phải Dependency Injection cụ thể cài đặt trong CAB. Mục tiêu hiểu khái quát về khái niệm Dependency Injection.

Giả sử class Car có đang có nhu cầu sử dụng class EngineA. Tuy nhiên có khả năng class Engine sẽ thay đổi trong tương lai hoặc có nhu cầu sử dụng nhiều kiểu Engine khác nhau. Nếu sử dụng thông qua một interface IEngine, khi chuyển qua sử dụng một loại Engine khác là EngineB sẽ không cần thực hiện modify lại code mà chỉ cần thực hiện config lại theo 1 cách nào đó.

Scenario thứ 2 là việc sử dụng engine theo kiểu service yêu cầu nhiều dependency services khác tạo thành một dependency graph. Do đó mỗi khi thực hiện tạo một instance của class Car rất mất công và bạn phải biết constructor hay builder của Engine.

var generator = new Generator();
var engine = new Engine(generator);
var car = new Car(engine);

Có thể thấy một chút giống Strategy Pattern chỉ khác Dependency Injection thiên về việc cấu hình để có thể chọn lựa được class sử dụng tại thời điểm run-time. Việc này có thể thực hiện bằng các ngôn ngữ hỗ trợ reflection. Có 3 loại Dependency Injection là thực hiện qua setter injection, constructor injectioninterface injection. Việc thực hiện DI còn liên quan đến 1 việc gọi là service locator. Nhiệm vụ của service locator là xác định đúng engine cần dùng thông qua GetCorrectEngine method. Thông thường một class giả sử tên là EngineLocator sẽ thực hiện nhiệm vụ này.

Constructor method

public interface IEngine
{
    void Start();
}

public class Engine : IEngine
{
    public void Start();
}

public class Car
{
    protected IEngine engine;

    public Car(IEngine engine)
    {
        this.engine = engine;
    }
}

Setter method

public class Car
{
    protected IEngine engine;

    public IEngine Engine
    {
        set
        {
            engine = value;
        }
    }
}

Interface method

public interface IInjectEngine
{
    void InjectEngine(IEngine engine);
}

public class Car: IInjectEngine
{
    protected IEngine engine;

    void IInjectEngine.InjectEngine(IEngine engine)
    {
        this.engine = engine;
    }
}

Thông thường việc thực hiện sẽ dùng hướng interface injection. Đến đây nếu chưa hiểu rõ DI chắc chắn bạn vẫn chưa hình dung được việc tách biệt interfaceimplement là có ý nghĩa như thế nào và tại sao Car không cần thiết phải chỉnh sửa mà chỉ cần chỉnh sửa thông qua file config.

Implementation

Lập một solution tên là Part03A với các project như sau:
  1. Car: console application, có một file là Car.cs, assembly name là ConsoleApp, output ConsoleApp.exe.
  2. EngineA: library project, chứa EngineA.cs, assembly EngineA.
  3. EngineB: library project, chứa EngineB.cs, assembly EngineB.
  4. IEngine: library project, chứa định nghĩa interface.
- Để đơn giản các project đang sử dụng cùng một namespace là DI. Cấu hình Assembly name và default namespace trong Properties của project.

- Bước tiếp theo cấu hình Output của các project giả sử là '..\bin\Debug' mục đích các file IEngine.dll, EngineA.dll và EngineB.dll sẽ cùng thư mục với ConsoleApp.exe (vì các projects này không reference đến nhau)

- Thêm vào Car project một application config file App.config. Add reference tới IEngine cho 3 project còn lại. Việc này có nghĩa Car không có reference tới bất kỳ cài đặt nào của Engine: EngineA hoặc EngineB. Việc này cho phép luôn luôn build OK với Car cho dù có hay không có EngineA và EngineB, do đó việc develop Car là hoàn toàn độc lập với Engine.

Thực hiện code của các file như sau:

IEngine.cs

namespace DI
{
    public interface IEngine
    {
        void Start();
    }
}

IInjectEngine.cs

namespace DI
{
    public interface IInjectEngine
    {
        void InjectEngine(IEngine engine);
    }
}

EngineA.csEngineB.cs

public class EngineA : IEngine
{

    public void Start()
    {
        Console.WriteLine("Engine A start...")
    }
}

public class EngineB : IEngine
{

    public void Start()
    {
        Console.WriteLine("Engine B start...")
    }
}

Car.cs

namespace DI
{
    public class Car : IInjectEngine
    {
        IEngine engine;

        public void Start()
        {
            engine.Start();
        }

        public void InjectEngine(IEngine engine)
        {
            this.engine = engine;
        }
    }
}

Program.cs

namespace DI
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create our car and inject the dependency
            Car car = new Car();

            // Service locator, get the correct address based on configuration file
            IEngine engine = GetCorrectEngine();
            ((IInjectCar)car).InjectEngine(engine);

            // Use the car, the method references the car
            // so behavior depends on the configuration file
            car.Start();
            Console.ReadLine();
        }

        // Instantiate and return a class conforming to the IEngine interface:
        // which class gets instantiated depends on the ClassName setting in
        // the configuration file
        static IEngine GetCorrectEngine()
        {
            string className = System.Configuration.ConfigurationSettings.AppSettings["ClassName"];
            Type type = System.Type.GetType(className);
            return (IEngine)Activator.CreateInstance(type);
        }
    }
}

- Method GetCorrectEngine đọc file config và tạo một instance của Engine đúng với implement cần chỉ định thông qua reflection.

- Nếu nội dung file config App.config không đúng (không có key ClassName) việc build vẫn OK tuy nhiên sẽ gặp run-time error vì không tạo được object. Cần phải chỉ định assembly name.

xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="ClassName" value="DI.EngineA" />
  </appSettings>
</configuration>

- Chỉnh lại file config tốt nhất là dùng AssemblyQualifiedName hoặc đơn giản chỉ cần dùng "DI.EngineA, EngineA"

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="ClassName" value="DI.EngineA, EngineA, Version=1.0.0.0, Culture=neutral, PublicKey Token=null" />
  </appSettings>
</configuration>

- Thực hiện thay đổi file config dùng EngineB và run lại application, nhận xét và rút ra kết luận.

2010-01-17

CAB & SCSF - Part 02: WorkItems

-->
WorkItems

Part 2: Overview khái niệm WorkItem.

WorkItems – Basic Concepts

Theo tài liệu của Microsoft mô tả là một run-time container chứa các component hợp tác với nhau thực hiện chức năng của một use case (a run-time container of components that are collaborating to fulfill a use case). Có thể xem WorkItems là các class trong đó có chứa các collection của các class khác. WorkItem trích từ metadata như sau:

using Microsoft.Practices.CompositeUI.Collections;
using Microsoft.Practices.CompositeUI.Commands;
using Microsoft.Practices.CompositeUI.EventBroker;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.Utility;
using Microsoft.Practices.ObjectBuilder;
using System;
using System.ComponentModel;
using System.Diagnostics;

namespace Microsoft.Practices.CompositeUI
{
    public class WorkItem : IBuilderAware, IDisposable
    {
        public WorkItem();

        public ManagedObjectCollection<Command> Commands { get; }
        public ManagedObjectCollection<EventTopic> EventTopics { get; }
        public string ID { get; set; }
        protected Builder InnerBuilder { get; }
        protected IReadWriteLocator InnerLocator { get; }
        public ManagedObjectCollection<object> Items { get; }
        [Dependency(NotPresentBehavior = NotPresentBehavior.ReturnNull)]
        [Browsable(false)]
        public WorkItem Parent { get; set; }
        [Browsable(false)]
        public WorkItem RootWorkItem { get; }
        public ServiceCollection Services { get; }
        public ManagedObjectCollection<object> SmartParts { get; }
        public State State { get; }
        public WorkItemStatus Status { get; }
        public TraceSource TraceSource { set; }
        public UIExtensionSiteCollection UIExtensionSites { get; }
        public ManagedObjectCollection<WorkItem> WorkItems { get; }
        public ManagedObjectCollection<IWorkspace> Workspaces { get; }

        public event EventHandler Activated;
        public event CancelEventHandler Activating;
        public event EventHandler Deactivated;
        public event CancelEventHandler Deactivating;
        public event EventHandler Disposed;
        public event EventHandler<DataEventArgs<string>> IdChanged;
        public event EventHandler Initialized;
        public event EventHandler RunStarted;
        public event EventHandler Terminated;
        public event EventHandler Terminating;

        public void Activate();
        protected internal void BuildUp();
        protected virtual Command CreateCommand(Type t, string name);
        protected virtual EventTopic CreateEventTopic(Type t, string topicName);
        public void Deactivate();
        public void DeleteState();
        public void Dispose();
        protected virtual void Dispose(bool disposing);
        protected internal void FinishInitialization();
        public TSmartPartInfo GetSmartPartInfo<TSmartPartInfo>(object smartPart) where TSmartPartInfo : ISmartPartInfo;
        protected internal void InitializeRootWorkItem(Builder builder);
        protected virtual void InitializeServices();
        [InjectionMethod]
        public void InitializeWorkItem();
        public void Load();
        protected virtual void OnActivated();
        protected virtual void OnActivating(CancelEventArgs args);
        public virtual void OnBuiltUp(string id);
        protected virtual void OnDeactivated();
        protected virtual void OnDeactivating(CancelEventArgs args);
        protected virtual void OnDisposed();
        protected virtual void OnIdChanged();
        protected virtual void OnInitialized();
        protected virtual void OnObjectAdded(object item);
        protected virtual void OnObjectRemoved(object item);
        protected virtual void OnRunStarted();
        public virtual void OnTearingDown();
        protected virtual void OnTerminated();
        protected virtual void OnTerminating();
        public void RegisterSmartPartInfo(object smartPart, ISmartPartInfo info);
        public void Run();
        public void Save();
        public void Terminate();
    }
}

Dễ dàng nhận thấy WorkItem có các 3 collection như sau:
1.      Items là một collection loại object nên có thể contains tất cả mọi thứ.
2.      Services collection chứa các CAB services (sẽ đề cập sau)
3.      WorkItems collection là một collection base trên WorkItem. Đây là các child WorkItems. Collection này thể hiện Composite pattern mà chúng ta đã tìm hiểu.
4.      Các collection khác SmartParts, UIExtensionSites, Workspaces.

WorkItem còn có State để theo dõi sự thay đổi trạng thái implement ISerializable và Status chỉ định là active hay inactive.

Theo mô tả ở trên sẽ thấy có rất nhiều thuật ngữ chưa đề cập và các thuật ngữ này rất mới và khó hiểu.

Container Hierarchy và Root WorkItem

Các WorkItems có thể biểu diễn dưới dạng phân cấp. Theo như lab 1 thì program có một RootWorkItem và Shell là một instance của ShellForm. Blue module và Red module sẽ được load vào RootWorkItem. WorkItem child có thể được truy cập qua code dạng như sau:

this.WorkItem.WorkItems["SpecificName"]

Đến đây chúng ta có thể mơ hồ nhận ra mối quan hệ giữa CAB và Composite pattern mà chúng ta đã tìm hiểu.

WorkItems và FormShellApplication
Ví dụ như trong lab về module loader của Part 1, Program chứa top-root WorkItem và được truy cập thông qua code:
this.RootWorkItem

Program cũng chứa một Shell có thể truy cập qua code:
this.Shell

Notes: hiện tại lab module loader Part 1 vẫn chưa thực hiện dùng hay truy cập thông qua các properties này. Các bài labs tiếp theo sẽ thực hiện trên các property này.

Trong phần tiếp theo chúng ta sẽ tìm hiểu cách thực hiện việc load modules cụ thể như thế nào. Đối tượng để tìm hiểu là Dependency Injection, một khái niệm quan trọng.