Nine MVP's Blog

21/02/2011

DI Framework Series : ออกแบบระบบให้ยืดหยุ่นด้วย Dependency Injection Framework

Filed under: Design Pattern — Tags: , , , , — Nine MVP @ 6:48 am

Series

  • DI Framework Series : ออกแบบระบบให้ยืดหยุ่นด้วย Dependency Injection Framework
  • DI Framework Series : ออกแบบ DAL ให้รอบรับอนาคตด้วย Repositoty Pattern

Programming Level:

  • Intermediate – Advance

Computer Skills:

  1. Object Oriented Programming (Interface/Abstract programing)
  2. Layer Architecture
  3. C# 3.0, LINQ
  4. ASP.NET

Development Tool and Library

  1. Visual Studio 2010

Background: Application Layer Design

มาถึงตอนสำคัญอีกตอนครับ สำหรับกลุ่มนักพัฒนาที่ชอบออกแบบและเขียนโปรแกรม  ในตอนนี้ผมขอเสนอเรื่องราวเกี่ยวกับการออกแบบระบบโดยใช้ PoEAA ตัวที่เรียกว่า Inversion of Control โดยวิธี Dependecy Injection ซึ่งเป็นเทคนิคและแนวทางที่กำลังนิยมอยู่ในปัจจุบัน ซึ่งช่วยให้ระบบมีความยืดหยุ่นในการทำงานทุกอย่าง แต่ก็มีข้อแลกเปลี่ยนซึ่งเป็นข้อเสียเช่นกัน  แต่เมื่อประมาณการแล้วข้อเสียนั้นเล็กน้อยมากเมื่อแลกกับข้อดีที่ได้มาเต็ม ๆ

แต่ก่อนที่ผมจะเข้าสู่การอธิบาย เจ้าด้วย DI ผมขอยกเรื่องราวตัวอย่างในปัญหาการพัฒนาโปรแกรมในชีวิตจริง ของพวกเรา ๆ ที่เริ่มเขียนโปรแกรมไว้ประมาณนี้ครับ

นายเอิธเป็นโปรแกรมเมอร์ที่เพิ่งเรียนจบ ตั้งใจจะรับงานฟรีแลนซ์ กะว่าทำงานคนเดียวคงรวยไม่ต้องแบ่งใคร ในเดือนแรกนายเอิธได้รับงานมาทำเป็น Windows App  ได้ไปเก็บรีไควเม้นท์ได้ ER Diagram, DB Dictionary, Screen  และ business กลับมา  จากนั้นก็เริ่มเขียนโปรแกรม โดยเริ่มจากการออกแบบดาต้าเบส  และเปิด visual studio ขึ้นมา และสร้างโปรเจ็คชื่อว่า EarthAccounting และสร้างหน้าจอสำหรับแสดงผลและบันทึกข้อมูล  โดยนายเอิธ มักจะกดดับเบิ้ลคลิ๊กปุ่มเพื่อให้เกิด click event แล้วก็เริ่มเขียนบันทึก อัพเดทข้อมูลกันภายในอีเว้นท์นั้น   นายเอิ๊กต้องทำ Form ของโปรแกรม 50 Form เลยทีเดียว  ปรากฎว่าเหนื่อยมาก  เวลาที่จะต้องส่งงานก็ใกล้เข้ามา  เงินที่ฝันไว้กำลังจะโดนหักหากส่งงานไม่ทันตามกำหนด  เนื่องจากทำอยู่คนเดียวงานจึงเสร็จล่าช้า  จึงตัดใจยอมดึงเพื่อนที่จบมาด้วยกันมาช่วยงานโดยแบ่ง form ให้ไปช่วยทำ   ทำไปได้ซักพักปรากฎว่าเริ่มมีฟังชั่นที่ใช้งานซ้ำ ๆ เกิดขึ้นจำนวนมากภายในฟอร์ม  นายเอิธจึงตกลงกับเพื่อนอีกคนในทีมว่า “เพื่อน เราจะแยกฟังชั่นที่ใช้ซ้ำ ๆ พวกนี้ไว้ใน utility class นะ  แล้วเพื่อนค่อยเอาไปใช้นะ”   หลังจากตกลงกันได้นายเอิธและเพื่อนก็เริ่ม refactoring code ในส่วนของฟังชั่นที่ได้ตกลงกันไว้  หลังจากทำงานไปได้ครึ่งทางปรากฎว่าเกิดมีการเปลี่ยน requirement โดยของแก้ business ส่วนกลางที่ใช้ในการคำนวนค่าทางบัญชี โดยการเปลี่ยนครั้งนี้กระทบกับ form ทั้งหมด 25 form 50 function เนื่องจากเป็นส่วนคำนวนที่ฝังโค้ดเอาไว้ในปุ่ม และยังมีงานเหลืออีก 15 Form ตั้งใจว่าจะ out source งานส่วนนี้ให้มือปืนรายอื่นเข้ามาช่วยงาน  แต่ต้องพบกับปัญหาที่ว่าไม่สามารถบอกโครงสร้างทางบิสซิเนสของลูกค้าให้บุคคลอื่นที่นอกเหนือจากสัญญาจ้างที่ได้เซ็นกันไว้ก่อนนี้  จนนายเอิธรู้สึกท้อใจกับการทำงานแนวทางนี้ จึงได้เปิดเว็บไปหากูเกิ้ล พบทางสว่างแห่งการลดปัญหาที่ว่า สามารถแบ่งงานกันทำได้ แก้ไขแล้วไม่กระทบระบบมากนัก ลดความซ้ำซ้อนและกระจายตัวของระบบงาน ก็คือการแบ่งโค้ดออกเป็น Layer โดยนายเอิธเห็นแนวทางว่าต้องแบ่งระบบออกเป็น 3 Layer (3 project) โดยมี

1. Presentation Layer (PL)  ส่วนที่ใช้แสดงผลได้แก่พวกกลุ่ม windows form, web form เป็นต้น

2. Business Logic Layer (BLL) แยกส่วนที่บรรจุลอจิกทาง business ออกมารวมกันไว้เป็นคลาส

3. Data Access Layer (DAL) เป็นส่วนที่ใช้ติดต่อกับดาต้าเบส

โดยทั้ง 3 layer มีความสัมพันธ์กันดังนี้ PL –> BLL –> DAL โครงสร้างที่นายเอิธใช้ในรอบแรกมีลักษณะเป็นแบบนี้ครับ

Sample01 Pattern : Basic structure

เริ่มต้นเริ่มแรกใครได้ลองเขียนแยก layer ของโปรเจ็คออกเป็นชิ้นย่อย ๆ PL,BLL,DAL หากจุดไหนใครอยากเรียกใครก็แค่ add reference เข้ามาแล้วก็สร้างอินแสตนท์และเรียกใช้งานกันตรงนั้นดังตัวอย่างนี้

image

กรณีใน Business Logic Layer (BLL) ที่จะเรียกใช้ Data Access Layer (DAL)image

จาก class diagram จะพบว่า CustomerBLL มี association กับ CustomerDAL ตรง ๆ อีกนัยก็คือได้ add reference ของ project เข้ามาใช้งาน ลองดูโค้ดด้านล่างนี้ครับ

image

จากภาพจะพบได้ว่าใน line 3,7 มีการประกาศตัวแปรทีและสร้างอินสแตนท์ของ CustomerDAL ขึ้นใช้งาน ซึ่งเป็นการอ้างถึง DAL ตรง ๆ จะทำให้เราต้องมีการสร้าง DAL ขึ้นก่อน ถึงจะสามารถเขียน BLL ต่อไปได้ หรือแม้กระทั่งการทำเทสก็ยากต่อการทดสอบในกรณีที่ต้องแยกเทสแต่ละ Layer เพราะ BLL ไม่สามารถเทสโดยข้ามการสร้างอินแสตนท์ของ DAL ไปได้ และ DAL ยังถูกเรียกใช้ในทุกๆ จุดของเม็ธธอดของ BLL นั่นเอง

และในส่วนของ dependency diagram จะเห็นว่ามี 2 DLL ที่เรียกใช้งานกันตรง ๆ ซึ่งเป็นคำพูดที่ว่า Tight Coupling นั่นเอง

image

ต่อมาแหม่ ทำงานพอได้ระบบใหญ่เข้าหน่อยชักฮึกเหิม ไม่นานนายเอิธก็บรรเจิดปิ๊งไอเดียที่ว่า อยากให้ EarthAccounting รองรับดาต้าเบสได้มากกว่า 1 ชนิด ต้องทำอย่างไร แน่นอนครับพึ่งพากูเกิ้ลอีก เสริชเข้าไปจนได้ความรุ้ที่ว่า “ก็ใช้ Class/Interface + Factory Method Pattern สิคร้าบบ” นายเอิธไม่รอนานรีบก่อนจะสายเวลาลงมือรื้อ BLL และ DAL ใหม่ทั้งยวง ตามตัวอย่างที่ 2 ข้างล่างนี้

Sample02 Pattern : Interface and Factory Class

image

ตัวอย่างที่2 ในกรณีที่ว่า ระบบต้องการที่จะรองรับ DATABASE มากกว่า 1 ชนิด ในที่นี้ผมมี DAL ที่เอาไว้ใช้ทั้ง MySql และ MSSql ไว้ซัพพอร์ทลูกค้ากรณีเขาเลือกใช้อย่างใดอย่างหนึ่ง เราจึงแยกโครงสร้างโปรเจ็คไว้ประมาณหน้าตาดังรูปด้านบนภาพด้านล่างนี้เป็น DAL Class Structure ครับ

image

1. ผมกำหนดโครงสร้างของ DAL ไว้เป็น Interface ที่ชื่อว่า ICustomerServiceDAL ซึ่งมีเม็ธธอด GetCustomerName() ไว้ที่โปรเจ็ก Domain02

2. คลาสที่ใช้ติดต่อกับดาต้าเบสทั้ง MySqlDAL02.MySqlDAL และ MSSQLDAL02.MSSQLDAL จะ implement Domain02.ICustomerServiceDAL ทั้งคู่

3. โปรเจ็ก DALFactory Class จะมี GetActiveDAL method โดยมี return type เป็น ICustomerServiceDAL และภายในจะมีการสร้างอินแสตนท์ของคลาส MySqlDAL, MSSQLDAL โดยตามที่อ่านค่าได้จาก configuration file ตามโค้ดด้านล่างนี้

image

4. ในโปรเจ็ค BLL02.CustomerBLL Class จะมีการเรียกใช้ Domain02.ICustomerServiceDAL และ DALFactory02.DALFactory ภายในดังนี้

image

ต่อมาลองดู dependency diagram ของตัวอย่างที่ 2 กันครับ

image

จากรูปด้านบนจะเห็นได้ว่าคลาส BLL02.CustomerBLL นั้นไม่ได้เรียกใช้ MSSqlDALและ MySqlDAL ตรง ๆ แต่จะเรียกใช้ผ่านคลาส DALFactoty02.DALFactory โดยอิงตามเสปคของ Domain02.ICustomerServiceDAL interface ซึง DALFactory เป็นคลาสที่ช่วยแยก DAL ของ Database ชนิดต่าง ๆ ออกไปจัดการภายใน factory method  ตรงนี้จุดที่เกิด tight coupling คือ CustomerBLL และ DALFactory

และในส่วนของ  Factory Class ก็ต้องรู้จักกับคลาส SqlDAL, MySqlDAL และ OracleDAL ตรง ๆ ซึ่งจากดีไซน์นี้ความเป็นจริงคือไม่ได้ลดความ Tight Coupling ของโครงสร้างลงเลย  แต่ว่าดีไซน์นี้มีกลิ่นไอของ Dependency Injection อยู่ระดับนึง

หลังจากที่นายเอิธพยายามกับโปรแกรมสุดเดิ้ล อย่าง EarthAccounting เพื่อเพิ่มความสามารถและสร้างความสามารถที่รองรับกับความต้องการของลูกค้าในเรื่องการเลือก database ได้ถึง 2 ชนิด  กลับมาเจอกับ challenge ใหม่ในหัวข้อที่ว่า ลูกค้าต้องการลอจิกในการคำนวณค่าบางตัวด้วยสูตรทางคณิตที่ซับซ้อน ซึ่งนายเอิธและเพื่อนไม่เก่งคณิตเอาซะเลย เลยตั้งใจจะ outsource งานส่วนนี้ไปให้คนอื่นทำ แต่มาติดปัญหาว่า คลาสใน BLL นั้นต้องมานั่งรอ คลาสอีกตัวของทาง outsource กว่าจะได้จัดส่ง มาให้ใช้งานใน BLL ถ้านั่งรอก็คงกินเวลาไปอีกเป็นอาทิตย์  ไหนกว่าจะได้งานมาก็มานั่งเขียนโปรแกรม นั่งเทส นั่ง build ทำ package งานเกินเวลาส่งแน่ ๆ จึงได้เปิดกูเกิ้ลและค้นหาจนได้พบกำคำว่า Dependency Injection

จากเรื่องราวด้านบน ทำให้เห็นได้ว่าสิ่งที่นายเอิธกำลังต้องการก็คือวิธีีการออกแบบและเฟรมเวิร์กที่จะช่วยแก้ปัญหา

1. ต้องการดีไซน์ที่ช่วยให้แยก Layer และ Dependency ให้เป็นอิสระจากกัน

2. ต้องการแยกเทสในแต่ละ Layer และ Dependency ที่เกี่ยวข้อง

3. ต้องการลดการ rebuild โปรแกรม กรณีที่มีการ update  DLL เพียงแค่บางตัว

4. สามารถกำหนดโครงสร้างของระบบ โดยใช้ Class, Interface เป็น contract ในการพัฒนาระหว่าง Layer และ Dependency ทั้งหมด

Dependency Injection Pattern

ถูกคิดค้นโดยนาย Martin Flower ในหัวข้อเรื่อง Inversion of control (IoC) มีวัตถุประสงค์เพื่อช่วยแก้ปัญหาการยึดเกาะระหว่างโมดูลของระบบให้แยกขาดออกจากกัน โดยกลับเส้นของ dependency จากผู้เรียกใช้กลายเป็นผู้ถูกเรียก (Hollywood Principal)   IoC เป็น 1 ในกลุ่มของ PoEAA โดยมีแนวทางการนำไปใช้งานได้ 2 แบบคือ Service Locator และ Dependency Injection   ซึ่งจะขอกล่าวถึงเพียง DI เท่านั้น  ดังนี้ผมขออธิบายแบบไม่อิงภาษา oo หรือแบบบ้าน ๆ ว่า DI นั้นคือการสร้างและยิง object ณ ตอน runtime เข้าไปยังกลุ่มคลาสที่ได้เรียกใช้ Interface ซึ่งเป็นข้อตกลงที่ object นั้นๆได้นำไป implement โดยการยิง object นั้นมีทั้งแบบ setter และ constructor

การนำ DI Pattern ไปใช้งานนั้นมีได้หลายวิธีครับ แบบ DIY (Do it your self) หรือใช้ Framework ซึ่งในปัจจุบันนั้นมีอยู่เยอะมากกกก ตามรายชื่อที่พอจะรวบรวมมาได้ด้านล่างนี้ (แต่ยังคงมีออีกเยอะ)

Name Performance(Transient : Singleton)* Current Version
Spring.Net n/a:n/a 1.3.1
Castle Windsor 2:4 2.5.1
Ninject 2:2 2.1.0.76
StructureMap 5:4 2.6.1.0
Unity (Microsoft) 4:4 2.0
AutoFac 3:4 2.2.4.9
MEF (Microsoft) N/A 1.0
Dynamo 5:5 1.0(beta)

* performance 1:bad,2:poor,3:normal,4:good,5:best

** มีหลายปัจจัยสำหรับการตัดสินใจเลือกใช้ framework เพราะแต่ละตัวนั้นมีความสามารถไม่เท่ากัน บางตัวสร้างมาเพื่อ DI โดยเฉพาะ บางตัวเป็น framework ที่มีการ integrate กับหลายส่วนงาน

DI Start up : Demo ASP.NET 3 Layer with Dependency Injection (Unity 2.0)

สำหรับเริ่มกับ DI ผมขอแนะนำการใช้งานเจ้า DI ตัวนึงก็คือ Unity ครับ เป็นของไมโครซอฟเองก่อนอื่นก็ไปดาวน์โหลดมาติดตั้งก่อนครับ ที่นี่ Unity 2.0 ติดตั้งเสร็จแล้วก็เริ่มใช้งานกันเลย

ลองดูโครงสร้างโปรเจ็คทั้งหมดครับ

image

และให้ add reference ตาม dependency diagram ตามภาพด้านล่างนี้

image

เราจะเห็นได้ว่า ไม่มีเส้น PL ไปเรียก BLL หรือ BLL ไปเรียก DAL ให้เห็นเลย ทุก Layer จะวิ่งไปมองที่ Domain ทั้งหมด

1. Domains03 Project

image

สำหรับโปรเจ็คนี้ ผมออกแบบไว้เพื่อกำหนดโครงสร้าง layer ทั้งหมดของ Application โดยภายในจะประกอบไปด้วย  Domain class เป็นกลุ่มคลาสที่เอาไว้ใช้งานข้ามระหว่าง layer , Service Interface เอาไว้กำหนดโครงสร้างให้ BLL , Repository Interface เอาไว้กำหนดโครงสร้างให้ DAL

    ตามรูปด้านล่างนี้

image

  • สำหรับ Domain03.Customer class เป็นโดเมนคลาส ที่จะใช้ในการทำงานกับทุก layer

    public class Customer
    {
       public string CustId { get; set; }
       public string FName { get; set; }
       public string LName { get; set; }
    }

  • ข้อกำหนดของ DAL ผมได้สร้าง Domain03.ICustomerRepository interface เอาไว้ใช้เป็นข้อกำหนด  โดยจะสร้างด้วยอะไรก็ได้ไม่ว่าจะเป็น Entity Framework, NHibernate หรือ Custom DAL with ADO.NET ก็ได้แล้วแต่ตามต้องการ  ซึ่งบังคับว่าคุณต้องใช้ interface ตัวนี้ในการทำงานเท่านั้น

public interface ICustomerRepository
{
   Customer GetCustomerById(string custId);
}

  • ผมสร้างส่วนของ Domain03.CustomerService interface เป็นข้อกำหนดว่า Busines Logic Class ของ CustomerService ใดๆที่จะพัฒนาและนำเข้ามาใช้งานต้องนำ interface นี้ไปใช้เท่านั้น

public interface ICustomerService

{
   Customer GetCustomer(string custId);
}

 

2. L2SDAL03 Project (DAL –> Domain)

image

สำหรับโปรเจ็คนี้ ผมได้นำ interface ที่ชื่อ ICustomerRepository เข้ามาใช้งานใน CustomerLinq2Sql Class โดยตามภาพไดอะแกรมด้านล่างนี้

image

ในส่วนของโค้ดผมได้สร้าง data สำหรับส่งค่ากลับไป ไม่ได้ติดต่อดาต้าเบสจริง ๆ ดังนี้

    public class CustomerLinq2Sql : ICustomerRepository
{
public Customer GetCustomerById(string custId)
{
if (!string.IsNullOrEmpty(custId))
{
var cust = new Customer{CustId = custId};                if (custId.ToLower().Contains(‘1’))
{
cust.FName = “Nine”;
cust.LName = “MVP”;
}
if (custId.ToLower().Contains(‘2’))
{
cust.FName = “SHISUKA”;
cust.LName = “SHAREPOINT”;
}
if (custId.ToLower().Contains(‘3’))
{
cust.FName = “JIENT”;
cust.LName = “BizTalk”;
}
return cust;
}
return null;
}
}

3. BLL03 Project  (BLL –> Domain)

image

ในส่วนของ BLL03 Project ก็คือ Business Logic Layer ของเรานั่นเอง ผมได้นำ ICustomerService interface เข้ามาอิมพลีเม้นให้กับคลาส CustomerService ตามไดอะแกรมด้านล่างนี้

image

ลองมาดูส่วนของโค้ดในคลาส CustomerService กันครับ

    public class CustomerService : ICustomerService
{
private ICustomerRepositorydal;        public CustomerService(ICustomerRepository repository)
{
dal = repository;
}

public Customer GetCustomer(string custId)
{
return dal.GetCustomerById(custId);
}

}

  • จะเห็นได้ว่าในบรรทัดที่ 3 จะมีการประกาศตัวแปรของ ICustomerRepository ไว้ที่นี่เนื่องจากวน BLL นั้นจะเป็นผ้ที่เรียกใช้ DAL นั่นเอง
  • บรรทัดที่ 5 คือการเปิดให้ DI นั้นยิง object เข้ามาทาง Constructor นั่นเอง
  • บรรทัดที่ 12 เป็นการเรียกใช้ DAL ที่ถูกยิง object เข้ามาโดย DI โดยเรียกผ่านทาง polymorphism ของ interface
4. WebUI03 Project (PL –> Domain)

หลังจากที่เราได้วางโครงสร้างและทำการสร้างกลุ่ม BLL, DALเสร็จเรียบร้อยแล้ว  ขั้นตอนต่อไปคือการนำไปใช้งาน โดยก่อนนี้การแบ่งเลเยอร์แบบปกติ ลำดับของผู้ที่จะทำการเรียกใช้ ก็คือ PL –> BLL –> DAL แต่เราจะเปลี่ยนเส้นให้ชี้ไปที่ PL –> Domain และสำหรับ PL นั่นจะมีส่วนที่พิเศษสำหรับการนำ DI เข้ามาใช้งาน เนื่องจากเป็นความแตกต่างของเทคโนโลยี ไม่ว่าจะเป็น ASP.NET, ASP.NET MVC, SilverLight, Windows App เป็นต้น

image

ขั้นแรก  ตั้งค่าคอนฟิกของ Unity ใน web.config  ดังนี้
  1. <configSections>
  2.   <sectionname=unitytype=Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration />
  3. </configSections>

 

เป็นการเพิ่มโมเดลคอนฟิกของ Unity ภายใน web.config

 

  1. <unity>
  2.   <typeAliases>
  3.     <!– Lifetime manager types –>
  4.     <typeAliasalias=singleton
  5.          type=Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
  6.              Microsoft.Practices.Unity />
  7.     <typeAliasalias=perThread
  8.          type=Microsoft.Practices.Unity.PerThreadLifetimeManager,
  9.              Microsoft.Practices.Unity />
  10.     <typeAliasalias=external
  11.          type=Microsoft.Practices.Unity.ExternallyControlledLifetimeManager,
  12.              Microsoft.Practices.Unity />
  13.   </typeAliases>
  14.   <containers>
  15.     <containername=containerOne>
  16.       <types>
  17.         <!– map ICustomerService to CustomerService –>
  18.         <typetype=Domains03.ICustomerService, Domains03mapTo=BLL03.CustomerService, BLL03>
  19.           <lifetimetype=singleton />
  20.         </type>
  21.         <!– map ICustomerRepository to CustomerLinq2Sql –>
  22.         <typetype=Domains03.ICustomerRepository, Domains03mapTo=L2SDAL03.CustomerLinq2Sql, L2SDAL03>
  23.           <lifetimetype=singleton />
  24.         </type>
  25.       </types>
  26.     </container>
  27.   </containers>
  28. </unity>

Line 2 – 13 คือการตั้งชื่อย่อให้ component กลุ่ม LifeTime

Line 15 คือการกำหนด container name ที่จะบรรจุกุล่ม dependency เข้าไว้ในนี้

Line 16 – 25 คือการแม๊พ Interface เข้ากับ Class ที่นำอินเทอเฟสไปใช้งาน โดยจะเป็น BLL และ DAL ของเรา

 

ขั้นที่ 2 แก้ไขไฟล์ Global.asax

เพราะว่าเว็บเวลาจะ start up ขึ้นมาส่วนที่ทำหน้าที่ก่อนคือไฟล์นี้ครับ โดยแก้ไขตามนี้

Code Snippet
  1. public interface IContainerAccessor
  2. {
  3.     IUnityContainer Container { get; set; }
  4. }
  5. public class Global : System.Web.HttpApplication, IContainerAccessor
  6. {
  7.     private static IUnityContainer _container;
  8.     public IUnityContainer Container
  9.     {
  10.         get { return _container; }
  11.         set { _container = value; }
  12.     }
  13.     private static void BuildContainer()
  14.     {
  15.         IUnityContainer container = new UnityContainer();
  16.         //unityContainer = new UnityContainer();
  17.         UnityConfigurationSection section
  18.             = (UnityConfigurationSection)ConfigurationManager.GetSection(“unity”);
  19.         section.Containers[“containerOne”].Configure(container);
  20.         _container = container;
  21.     }
  22.     private static void CleanUp()
  23.     {
  24.         if (_container != null)
  25.         {
  26.             _container.Dispose();
  27.         }
  28.     }
  29.     void Application_Start(object sender, EventArgs e)
  30.     {
  31.         // Code that runs on application startup
  32.         BuildContainer();
  33.     }
  34.     void Application_End(object sender, EventArgs e)
  35.     {
  36.         //  Code that runs on application shutdown
  37.         CleanUp();
  38.     }
  39.     void Application_Error(object sender, EventArgs e)
  40.     {}
  41.     void Session_Start(object sender, EventArgs e)
  42.     {}
  43.     void Session_End(object sender, EventArgs e)
  44.     {}
  45. }

line 1-4 เป็น interface ที่เอาไว้ให้ส่วนอื่นสามารถเรียกใช้ container ได้จาก Global

line 6 อิมพลีเม้น IContainerAccessor ให้ Global

line 9 เป็นการประกาศตัวแปรของ container

line 11 เป็น Property จาก IContainerAccessor Interface

line 17-25 เป็นการโหลด container จาก web.config ที่เราได้ตั้งค่าไว้เข้ามา

 

ขั้นที่ 3 จัดการ set dependency ไปยังหน้า webpage (Setter Method)

ซึ่งเราจะสร้าง HttpModule เอาไว้สำหรับสั่งให้ DI เซ็ทค่าต่าง ๆ เข้าไปใน Web UI ณ จุด ที่มีการเรียกใช้กลุ่ม อินเทอเฟส ในทุกครั้งที่มีการเรียกถึง page นั้น ๆ ดังนี้

DIHttpModule.cs
  1. public class DIHttpModule : IHttpModule
  2.     {
  3.         private IUnityContainer container;
  4.         private void ContextPreRequestHandlerExecute(object sender, EventArgs e)
  5.         {
  6.             Page page = HttpContext.Current.CurrentHandler as Page;
  7.             if (page != null)
  8.             {
  9.                 page.PreInit += Page_PreInit;
  10.             }
  11.         }
  12.         private void BuildUp(object o)
  13.         {
  14.             container.BuildUp(o.GetType(), o);
  15.         }
  16.         private void Page_PreInit(object sender, EventArgs e)
  17.         {
  18.             Page page = sender as Page;
  19.             BuildUp(page);
  20.             BuildUpMaster(page.Master);
  21.             BuildUpControls(page.Controls);
  22.         }
  23.         private void BuildUpControls(ControlCollection controls)
  24.         {
  25.             foreach (Control c in controls)
  26.             {
  27.                 if (c is UserControl)
  28.                     BuildUp(c);
  29.                 BuildUpControls(c.Controls);
  30.             }
  31.         }
  32.         private void BuildUpMaster(MasterPage page)
  33.         {
  34.             if (page != null)
  35.             {
  36.                 BuildUp(page);
  37.                 BuildUpMaster(page.Master);
  38.             }
  39.         }
  40.         #region IHttpModule Members
  41.         public void Init(HttpApplication context)
  42.         {
  43.             container = ((IContainerAccessor)context).Container;
  44.             context.PreRequestHandlerExecute += ContextPreRequestHandlerExecute;
  45.         }
  46.         public void Dispose()
  47.         {
  48.         }
  49.         #endregion
  50.     }

เข้าไป register HttpModule ที่เราสร้างขึ้นใน web.config ภายในบริเวณ <system.web> ..  ดังนี้

  1.     <httpModules>
  2.       <addname=DIModtype=WebUI03.IoC.DIHttpModule, WebUI03 />
  3.     </httpModules>

 

 

ขั้นที่ 4  ทดสอบเรียกใช้งาน BLL ด้วย ICustomerService

หลังจากที่ได้ตั้งค่าและสร้าง Helper ในส่วนของ DI กันเสร็จแล้ว คราวนี้เราก็มาใช้งานกันได้แล้วครับ โดยแก้ไขหน้า default.aspx ออกมาประมาณนี้

image

และเขียนโค้ดไว้ด้านหลังแบบนี้

Code Snippet
  1. using System;
  2. using Domains03;
  3. using Microsoft.Practices.Unity;
  4. namespace WebUI03
  5. {
  6.     public partial class _Default : System.Web.UI.Page
  7.     {
  8.         [Dependency]
  9.         public ICustomerService custSvc { get; set; }
  10.         protected void Button1_Click(object sender, EventArgs e)
  11.         {
  12.             var cust = custSvc.GetCustomer(txtCustId.Text);
  13.             if (cust == null)
  14.             {
  15.                 lbcustid.BackColor = System.Drawing.Color.Red;
  16.                 return;
  17.             }
  18.             lbcustid.Text = cust.CustId;
  19.             lbfname.Text = cust.FName ?? “not found”;
  20.             lblname.Text = cust.LName ?? “not found”;
  21.         }
  22.     }
  23. }

 

Line 9-10  เป็นการประกาศตัวแปรสำหรับรับ object ของ BLL เข้ามาโดยกำหนด attribute [Dependency] ไว้บนหัว property ICustomerService custSvc   เมื่อมีการเรียก page นี้ให้ทำงาน  เจ้า DIHttpModule ที่เราเขียนไว้จะมาตรวจจับ Dependency attribute และ set BLL object (BLL03.CustomerBLL) เข้ามาที่ property นี้

Line 14  เป็นการเรียกใช้ CustomerService เพื่อดึงข้อมูลผ่าน DAL อีกทีนึง

ผลลัพธ์ที่ได้

image

Conclusion

หลังจากที่ได้ทดลองทำลองอ่านตามที่ผมนำเสนอไป หวังทุกท่านคงพอมองเห็นลู่ทางในการนำไปใช้งานกันต่อ เนื่องจากเจ้า DI นี้ช่วยเพิ่มความยืดหยุ่นให้ระบบของเราได้มากจริง ๆ อาจจะมีข้อแลกเปลี่ยนบ้างเช่น ระบบในตอน startup จะช้ากว่าปกติบ้าง และอาจจะต้องระวังเรื่องหน่วยความจำด้วย

สำหรับตอนนี้ผมเขียนไปยาวมาก จริงแล้วยังไม่จบ ยังไม่ได้พูดถึงเรื่องการทำ Testing ในแต่ละ layer และการ integrate กับ ASP.NET MVC อีกทั้งยังมี DI Framework ที่น่าสนใจจะมาแนะนำอีกหลายยี่ห้อ  ค่อนข้างยาวครับ แต่ผมขอจบตอนไว้แค่นี้ก่อน ไว้จะมาเขียนต่อในเรื่องที่ได้บอกไปข้างต้น  สวัสดีครับ


 

สามารถ download source code ได้ที่นี่

Download Source Code Here

 

 

Chalermpon Areepong Nine (นาย)

Microsoft MVP Thailand ASP.NET

email : nine_biz-talk.net at hotmail dot com

5 Comments »

  1. ขอบคุณมากครับ

    Comment by lordtoro — 28/04/2011 @ 5:49 pm

    • ยินดีครับ

      Comment by Nine MVP ASP.NET — 03/05/2011 @ 9:25 am

  2. ขอบคุณสำหรับบทความดีๆ ครับ

    Comment by Theerapol Muangyoung — 15/06/2012 @ 10:53 am

  3. ขออนุญาติ นำบทความของพี่นาย มากลั่นกรองเป็นภาษาอังกฤษ แล้ว พรีเซนต์เจ้านายได้มั้ยครับ
    ไม่ลืมเครดิตแน่นอน

    Comment by Sakchai Phasuk — 24/02/2014 @ 3:09 pm

    • ยินดีครับ😀

      Comment by Nine MVP ASP.NET — 01/04/2014 @ 8:13 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: