MESSAGE #129281 (อ่าน 9,259 ครั้ง)
[Article] : ออกแบบระบบให้ยืดหยุ่นด้วย Dependency Injection (DI) Framework
Tags: Web, C#, .NET 4.0, VS 2010, OOAD/UML, Coding, Article
reference my blog : http://nine69.wordpress.com/
Programming Level:
- Intermediate – Advance
Computer Skills:
- Object Oriented Programming (Interface/Abstract programing)
- Layer Architecture
- C# 3.0, LINQ
- ASP.NET
Development Tool and Library
- 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 เข้ามาแล้วก็สร้างอินแสตนท์และเรียกใช้งานกันตรงนั้นดังตัวอย่างนี้
จาก class diagram จะพบว่า CustomerBLL มี association กับ CustomerDAL ตรง ๆ อีกนัยก็คือได้ add reference ของ project เข้ามาใช้งาน ลองดูโค้ดด้านล่างนี้ครับ
จากภาพจะพบได้ว่าใน line 3,7 มีการประกาศตัวแปรทีและสร้างอินสแตนท์ของ CustomerDAL ขึ้นใช้งาน ซึ่งเป็นการอ้างถึง DAL ตรง ๆ จะทำให้เราต้องมีการสร้าง DAL ขึ้นก่อน ถึงจะสามารถเขียน BLL ต่อไปได้ หรือแม้กระทั่งการทำเทสก็ยากต่อการทดสอบในกรณีที่ต้องแยกเทสแต่ละ Layer เพราะ BLL ไม่สามารถเทสโดยข้ามการสร้างอินแสตนท์ของ DAL ไปได้ และ DAL ยังถูกเรียกใช้ในทุกๆ จุดของเม็ธธอดของ BLL นั่นเอง
และในส่วนของ dependency diagram จะเห็นว่ามี 2 DLL ที่เรียกใช้งานกันตรง ๆ ซึ่งเป็นคำพูดที่ว่า Tight Coupling นั่นเอง
ต่อมาแหม่ ทำงานพอได้ระบบใหญ่เข้าหน่อยชักฮึกเหิม ไม่นานนายเอิธก็บรรเจิดปิ๊งไอเดียที่ว่า อยากให้ EarthAccounting รองรับดาต้าเบสได้มากกว่า 1 ชนิด ต้องทำอย่างไร แน่นอนครับพึ่งพากูเกิ้ลอีก เสริชเข้าไปจนได้ความรุ้ที่ว่า “ก็ใช้ Class/Interface + Factory Method Pattern สิคร้าบบ” นายเอิธไม่รอนานรีบก่อนจะสายเวลาลงมือรื้อ BLL และ DAL ใหม่ทั้งยวง ตามตัวอย่างที่ 2 ข้างล่างนี้
REPLY #1 (129282)
Sample02 Pattern : Interface and Factory Class
ตัวอย่างที่2 ในกรณีที่ว่า ระบบต้องการที่จะรองรับ DATABASE มากกว่า 1 ชนิด ในที่นี้ผมมี DAL ที่เอาไว้ใช้ทั้ง MySql และ MSSql ไว้ซัพพอร์ทลูกค้ากรณีเขาเลือกใช้อย่างใดอย่างหนึ่ง เราจึงแยกโครงสร้างโปรเจ็คไว้ประมาณหน้าตาดังรูปด้านบนภาพด้านล่างนี้เป็น DAL Class Structure ครับ
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 ตามโค้ดด้านล่างนี้
4. ในโปรเจ็ค BLL02.CustomerBLL Class จะมีการเรียกใช้ Domain02.ICustomerServiceDAL และ DALFactory02.DALFactory ภายในดังนี้
ต่อมาลองดู dependency diagram ของตัวอย่างที่ 2 กันครับ
จากรูปด้านบนจะเห็นได้ว่าคลาส 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 ทั้งหมด
REPLY #2 (129283)
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 ติดตั้งเสร็จแล้วก็เริ่มใช้งานกันเลย
ลองดูโครงสร้างโปรเจ็คทั้งหมดครับ
และให้ add reference ตาม dependency diagram ตามภาพด้านล่างนี้
เราจะเห็นได้ว่า ไม่มีเส้น PL ไปเรียก BLL หรือ BLL ไปเรียก DAL ให้เห็นเลย ทุก Layer จะวิ่งไปมองที่ Domain ทั้งหมด
1. Domains03 Project
สำหรับโปรเจ็คนี้ ผมออกแบบไว้เพื่อกำหนดโครงสร้าง layer ทั้งหมดของ Application โดยภายในจะประกอบไปด้วย Domain class เป็นกลุ่มคลาสที่เอาไว้ใช้งานข้ามระหว่าง layer , Service Interface เอาไว้กำหนดโครงสร้างให้ BLL , Repository Interface เอาไว้กำหนดโครงสร้างให้ DAL
- ตามรูปด้านล่างนี้
public class Customer
{
public string CustId { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
public class Customer
{
public string CustId { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
{
public string CustId { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
- สำหรับ Domain03.Customer class เป็นโดเมนคลาส ที่จะใช้ในการทำงานกับทุก layer
public interface ICustomerRepository
{
Customer GetCustomerById(string custId);
}
public interface ICustomerRepository
{
Customer GetCustomerById(string custId);
}
{
Customer GetCustomerById(string custId);
}
ผมสร้างส่วนของ Domain03.CustomerService interface เป็นข้อกำหนดว่า Busines Logic Class ของ CustomerService ใดๆที่จะพัฒนาและนำเข้ามาใช้งานต้องนำ interface นี้ไปใช้เท่านั้น
public interface ICustomerService
{
Customer GetCustomer(string custId);
}
ผมสร้างส่วนของ Domain03.CustomerService interface เป็นข้อกำหนดว่า Busines Logic Class ของ CustomerService ใดๆที่จะพัฒนาและนำเข้ามาใช้งานต้องนำ interface นี้ไปใช้เท่านั้น
public interface ICustomerService
{
Customer GetCustomer(string custId);
}
REPLY #3 (129284)
2. L2SDAL03 Project (DAL –> Domain)
สำหรับโปรเจ็คนี้ ผมได้นำ interface ที่ชื่อ ICustomerRepository เข้ามาใช้งานใน CustomerLinq2Sql Class โดยตามภาพไดอะแกรมด้านล่างนี้
ในส่วนของโค้ดผมได้สร้าง data สำหรับส่งค่ากลับไป ไม่ได้ติดต่อดาต้าเบสจริง ๆ ดังนี้
public class CustomerLinq2Sql : ICustomerRepository
{
public Customer GetCustomerById(string custId)
{
if (!string.IsNullOrEmpty(custId))
{
var cust = new Customer{CustId = custId};
{
public Customer GetCustomerById(string custId)
{
if (!string.IsNullOrEmpty(custId))
{
var cust = new Customer{CustId = custId};
if (custId.ToLower().Contains('a'))
{
cust.FName = "abcdefg";
cust.LName = "tryruyquwr";
}
if (custId.ToLower().Contains('ก'))
{
cust.FName = "ฟหกด่าวสาหกดวสา่";
cust.LName = "สวทหวดสเ่หกยเด่";
}
if (custId.ToLower().Contains('0'))
{
cust.FName = "29785740573";
cust.LName = "23758945897653";
}
return cust;
}
return null;
}
}
{
cust.FName = "abcdefg";
cust.LName = "tryruyquwr";
}
if (custId.ToLower().Contains('ก'))
{
cust.FName = "ฟหกด่าวสาหกดวสา่";
cust.LName = "สวทหวดสเ่หกยเด่";
}
if (custId.ToLower().Contains('0'))
{
cust.FName = "29785740573";
cust.LName = "23758945897653";
}
return cust;
}
return null;
}
}
3. BLL03 Project (BLL –> Domain)
ในส่วนของ BLL03 Project ก็คือ Business Logic Layer ของเรานั่นเอง ผมได้นำ ICustomerService interface เข้ามาอิมพลีเม้นให้กับคลาส CustomerService ตามไดอะแกรมด้านล่างนี้
ลองมาดูส่วนของโค้ดในคลาส CustomerService กันครับ
public class CustomerService : ICustomerService
{
private ICustomerRepository dal;
{
private ICustomerRepository dal;
public CustomerService(ICustomerRepository repository)
{
dal = repository;
}
{
dal = repository;
}
public Customer GetCustomer(string custId)
{
return dal.GetCustomerById(custId);
}
{
return dal.GetCustomerById(custId);
}
}
- จะเห็นได้ว่าในบรรทัดที่ 3 จะมีการประกาศตัวแปรของ ICustomerRepository ไว้ที่นี่เนื่องจากวน BLL นั้นจะเป็นผ้ที่เรียกใช้ DAL นั่นเอง
- บรรทัดที่ 5 คือการเปิดให้ DI นั้นยิง object เข้ามาทาง Constructor นั่นเอง
- บรรทัดที่ 12 เป็นการเรียกใช้ DAL ที่ถูกยิง object เข้ามาโดย DI โดยเรียกผ่านทาง polymorphism ของ interface
REPLY #4 (129289)
4. WebUI03 Project (PL –> Domain)
หลังจากที่เราได้วางโครงสร้างและทำการสร้างกลุ่ม BLL, DALเสร็จเรียบร้อยแล้ว ขั้นตอนต่อไปคือการนำไปใช้งาน โดยก่อนนี้การแบ่งเลเยอร์แบบปกติ ลำดับของผู้ที่จะทำการเรียกใช้ ก็คือ PL –> BLL –> DAL แต่เราจะเปลี่ยนเส้นให้ชี้ไปที่ PL –> Domain และสำหรับ PL นั่นจะมีส่วนที่พิเศษสำหรับการนำ DI เข้ามาใช้งาน เนื่องจากเป็นความแตกต่างของเทคโนโลยี ไม่ว่าจะเป็น ASP.NET, ASP.NET MVC, SilverLight, Windows App เป็นต้น
ขั้นแรก ตั้งค่าคอนฟิกของ Unity ใน web.config ดังนี้
- <configSections>
- <section name="unity"type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
- </configSections>
เป็นการเพิ่มโมเดลคอนฟิกของ Unity ภายใน web.config
- <unity>
- <typeAliases>
- <!– Lifetime manager types –>
- <typeAlias alias="singleton"
- type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
- Microsoft.Practices.Unity" />
- <typeAlias alias="perThread"
- type="Microsoft.Practices.Unity.PerThreadLifetimeManager,
- Microsoft.Practices.Unity" />
- <typeAlias alias="external"
- type="Microsoft.Practices.Unity.ExternallyControlledLifetimeManager,
- Microsoft.Practices.Unity" />
- </typeAliases>
- <containers>
- <container name="containerOne">
- <types>
- <!– map ICustomerService to CustomerService –>
- <type type="Domains03.ICustomerService, Domains03"mapTo="BLL03.CustomerService, BLL03">
- <lifetime type="singleton" />
- </type>
- <!– map ICustomerRepository to CustomerLinq2Sql –>
- <type type="Domains03.ICustomerRepository, Domains03"mapTo="L2SDAL03.CustomerLinq2Sql, L2SDAL03">
- <lifetime type="singleton" />
- </type>
- </types>
- </container>
- </containers>
- </unity>
Line 2 – 13 คือการตั้งชื่อย่อให้ component กลุ่ม LifeTime
Line 15 คือการกำหนด container name ที่จะบรรจุกุล่ม dependency เข้าไว้ในนี้
Line 16 – 25 คือการแม๊พ Interface เข้ากับ Class ที่นำอินเทอเฟสไปใช้งาน โดยจะเป็น BLL และ DAL ของเรา
REPLY #5 (129290)
ขั้นที่ 2 แก้ไขไฟล์ Global.asax
เพราะว่าเว็บเวลาจะ start up ขึ้นมาส่วนที่ทำหน้าที่ก่อนคือไฟล์นี้ครับ โดยแก้ไขตามนี้
Code Snippet
- public interface IContainerAccessor
- {
- IUnityContainer Container { get; set; }
- }
- public class Global : System.Web.HttpApplication,IContainerAccessor
- {
- private static IUnityContainer _container;
- public IUnityContainer Container
- {
- get { return _container; }
- set { _container = value; }
- }
- private static void BuildContainer()
- {
- IUnityContainer container = new UnityContainer();
- //unityContainer = new UnityContainer();
- UnityConfigurationSection section
- = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
- section.Containers["containerOne"].Configure(container);
- _container = container;
- }
- private static void CleanUp()
- {
- if (_container != null)
- {
- _container.Dispose();
- }
- }
- void Application_Start(object sender, EventArgs e)
- {
- // Code that runs on application startup
- BuildContainer();
- }
- void Application_End(object sender, EventArgs e)
- {
- // Code that runs on application shutdown
- CleanUp();
- }
- void Application_Error(object sender, EventArgs e)
- {}
- void Session_Start(object sender, EventArgs e)
- {}
- void Session_End(object sender, EventArgs e)
- {}
- }
line 1-4 เป็น interface ที่เอาไว้ให้ส่วนอื่นสามารถเรียกใช้ container ได้จาก Global
line 6 อิมพลีเม้น IContainerAccessor ให้ Global
line 9 เป็นการประกาศตัวแปรของ container
line 11 เป็น Property จาก IContainerAccessor Interface
line 17-25 เป็นการโหลด container จาก web.config ที่เราได้ตั้งค่าไว้เข้ามา
REPLY #6 (129292)
ขั้นที่ 3 จัดการ set dependency ไปยังหน้า webpage (Setter Method)
ซึ่งเราจะสร้าง HttpModule เอาไว้สำหรับสั่งให้ DI เซ็ทค่าต่าง ๆ เข้าไปใน Web UI ณ จุด ที่มีการเรียกใช้กลุ่ม อินเทอเฟส ในทุกครั้งที่มีการเรียกถึง page นั้น ๆ ดังนี้
DIHttpModule.cs
- public class DIHttpModule : IHttpModule
- {
- private IUnityContainer container;
- private void ContextPreRequestHandlerExecute(objectsender, EventArgs e)
- {
- Page page = HttpContext.Current.CurrentHandler asPage;
- if (page != null)
- {
- page.PreInit += Page_PreInit;
- }
- }
- private void BuildUp(object o)
- {
- container.BuildUp(o.GetType(), o);
- }
- private void Page_PreInit(object sender, EventArgs e)
- {
- Page page = sender as Page;
- BuildUp(page);
- BuildUpMaster(page.Master);
- BuildUpControls(page.Controls);
- }
- private void BuildUpControls(ControlCollectioncontrols)
- {
- foreach (Control c in controls)
- {
- if (c is UserControl)
- BuildUp(c);
- BuildUpControls(c.Controls);
- }
- }
- private void BuildUpMaster(MasterPage page)
- {
- if (page != null)
- {
- BuildUp(page);
- BuildUpMaster(page.Master);
- }
- }
- #region IHttpModule Members
- public void Init(HttpApplication context)
- {
- container = ((IContainerAccessor)context).Container;
- context.PreRequestHandlerExecute += ContextPreRequestHandlerExecute;
- }
- public void Dispose()
- {
- }
- #endregion
- }
เข้าไป register HttpModule ที่เราสร้างขึ้นใน web.config ภายในบริเวณ <system.web> .. ดังนี้
- <httpModules>
- <add name="DIMod" type="WebUI03.IoC.DIHttpModule, WebUI03" />
- </httpModules>
ขั้นที่ 4 ทดสอบเรียกใช้งาน BLL ด้วย ICustomerService
หลังจากที่ได้ตั้งค่าและสร้าง Helper ในส่วนของ DI กันเสร็จแล้ว คราวนี้เราก็มาใช้งานกันได้แล้วครับ โดยแก้ไขหน้า default.aspx ออกมาประมาณนี้
REPLY #14 (129305)
@Mr.L ไหน css ที่ขอ ด่วน ๆ
@Firelfy ครับ น่าจะโดนน่ะเรื่องนี้
@Nano ขอบคุณครับ เรื่องนี้กินแรงสุดๆแล้ว
@Tapeza555 ตอนต่อไปคงเน้น real world app แบบละเอียดจริง ๆ ตอนนี้ขอแบบร่าง ๆ ไปก่อน
@ekchaiii ยินดีด้วยครับ ที่โดน จะพยายามเข็ญตอนต่อไปออกมาไว ๆ
REPLY #16 (129329)
@figgaro ดีใจด้วยครับผม
ปล. ถึงเพื่อนรักนักอ่าน ทุกท่าน รบกวน feedback และ comment ให้ผมด้วยนะครับ จะได้เอาไปปรับปรุงเนื้อหา ในบทความต่อไปด้วย
ติได้ครับ เพราะผมจะได้ทราบว่าผิดตรงไหนจะได้แก้ไขคร้าบบ
สำหรับ background ที่ลายตานั้น เนื่องจากผม copy มาจาก http://Nine69.WordPress.com
ดังนั้นหากต้องการจะอ่านอย่างสบายตามี 2 ทางคือ
1. ใช้ Google Chrome ในการอ่าน
2. หรือไปอ่านที่ http://Nine69.WordPress.com
REPLY #17 (129335)
ขอมาช่วยกันแชร์ครับ :)
ถ้าเราต้องการเปลี่ยน Class Implement ได้ เราก็ต้อง new Class จากข้างนอกแล้วยัดใส่แบบนี้ OhMyGod!!
ICustomerService customerService = new CustomerService (
new CustomerLinq2Sql( )
, new Logging(), newASPNetCaching());
ดังนั้นตัว Container( Spring , Unity ,Castle Windsor...) ทำให้ Code เราง่ายขึ้น ถ้ามีการเปลี่ยนก็แค่ไป Map ใหม่
ICustomerService customerService = IoCFactory.Resolve<ICustomerService >();
นอกเรื่อง
ตอนนี้ผมกำลังทำ(เล่น) CassetteDev.Core เป็นเหมือนกับ Infrasructure ของ Application ให้ Layer อื่นๆเรียกใช้
แต่ก็ไม่รู้จะใช้ IoC Container ตัวไหนดีเยอะเกิ้น ไม่อยากให้มันผูกกับอันใดอันหนึ่ง ผมเลยเลือกใช้ Service Locator ช่วยให้เปลี่ยน
Container ได้ตามต้องการ ซึ่ง Microsoft.Practices.ServiceLocation เป็นทางเลือกที่ดีมากๆ
CassetteDev.Core
+ DependencyInjection
- BootStrapperManager.cs
- CommonBootStrapper.cs
- IoCFactory.cs
ผมลองเอามาทำต่อจากโปรเจ็กพี่นาย ได้ประมาณนี้ครับ
ทางฝั่ง UI (WebUI03 )
+ BootStrapper
- WindsorServiceLocator.cs (Implement ServiceLocatorImplBase เขียนเอง ส่วน Unity มีอยู่แล้วแจ๋วจริงๆ)
- UnityBootStrapper.cs (Implement CommonBootStrapper )
- WindsorBootStrapper.cs (Implement CommonBootStrapper เขียนเอง)
//UnityBootStrapper.cs
public class UnityBootStrapper: CommonBootStrapper {
protected override IServiceLocator CreateServiceLocator(){
IUnityContainer container = new UnityContainer();
RegisterTypes(container);
return new UnityServiceLocator(container);
}
private void RegisterTypes(IUnityContainer container) {
container.RegisterType<ICustomerRepository, CustomerLinq2Sql>();
container.RegisterType<ICustomerService, CustomerService>();
}
}
WindsorBootStrapper.cs
public class WindsorBootStrapper : CommonBootStrapper{
protected override IServiceLocator CreateServiceLocator() {
WindsorContainer container = new WindsorContainer();
RegisterTypes(container);
return new WindsorServiceLocator(container);
}
private void RegisterTypes(IWindsorContainer container) {
container.Register(Component.For<ICustomerRepository>().ImplementedBy<CustomerLinq2Sql>());
container.Register(Component.For<ICustomerService>().ImplementedBy<CustomerService>());
}
}
ที่ Global.cs
void Application_Start(object sender, EventArgs e)
{
BootStrapperManager.Initialize(new WindsorBootStrapper());
//BootStrapperManager.Initialize(new UnityBootStrapper());
}
REPLY #19 (129369)
ใช่ครับพี่นาย IDependencyResolver กับ IServiceLocator น่าจะเหมือนๆ กัน
public interface IServiceLocator : IServiceProvider
{
IEnumerable<TService> GetAllInstances<TService>();
IEnumerable<object> GetAllInstances(Type serviceType);
TService GetInstance<TService>();
TService GetInstance<TService>(string key);
object GetInstance(Type serviceType);
object GetInstance(Type serviceType, string key);
}
{
IEnumerable<TService> GetAllInstances<TService>();
IEnumerable<object> GetAllInstances(Type serviceType);
TService GetInstance<TService>();
TService GetInstance<TService>(string key);
object GetInstance(Type serviceType);
object GetInstance(Type serviceType, string key);
}
ปล. ไปครับไป ไว้เจอกันครับ
REPLY #22 (129421)
ทำตามแล้วมัน error ที่คำสั่ง
var cust = custSvc.GetCustomer(txtCustId.Text); //อยู่ที่หน้า Default.aspx.cs
แจ้ง error ว่า {"Object reference not set to an instance of an object."} watch ดู value มีค่าเป็น null ส่วนที่โปรเจคพี่นายผม watch ดู แล้วมีค่าเป็น BLL03.CustomerService
REPLY #23 (129422)
ตรวจสอบตามนี้น่ะ
1. web.config ได้ map ค่า library ใน unity container หรือยัง
2. มี dll จาก BLL, DAL ไปวางไว้ใน bin folder ของโปรเจ็ค web ui หรือยัง
3. ที่ global.asax.cs มีคอนฟิก code ตามที่บอกรึยัง
4. ICustomerService มี [Dependency] attribute กำหนดวางไว้หรือยัง
REPLY #24 (129423)
ปรับแล้วนะครับ ถ้าที่นายมีเวลาช่วยดูให้หน่อยละกันนะครับ แต่ถ้าไม่ว่างก็ไม่เป็นไรครับผมจะสร้างโปรเจคใหม่แล้วทำใหม่ดูเผื่อว่าผิดขั้นตอนไหน http://www.panitthon.ac.th:83/newtcc/files/DITestProject.zip อันนี้ผมเอาโปรเจคไว้ให้โหลด
REPLY #26 (129482)
สำหรับปัญหาของคุณ yokeyoke ตามตัวอย่างคุณยังขาดในส่วนของ DIHttpModule และการ Register DIHttpModule ที่ web.config ครับ
ปัญหาเกิดจากการที่ class _Default ไม่ได้ถูกตัว UnityContainer ทำ BuildUp Object ครับ
ปัญหาเกิดจากการที่ class _Default ไม่ได้ถูกตัว UnityContainer ทำ BuildUp Object ครับ
REPLY #27 (129488)
นิดนึงครับพี่นาย ผมอยากรู้ว่าถ้า หลังจากนายเอิธเขียนโปรแกรมตามแบบ DI แพทเทิลแล้วด้วย Unity2.0 นายเอิธกลับโดนให้แก้ส่วนของ ฺBusinessLogic และได้เพิ่มตารางมาอีกหนึ่งตาราง นายเอิธ จะทำยังไงครับ แล้วอยากให้พี่นายแสดงให้ดูว่า การออกแบบตามแบบ DI แพทเทิล ด้วย Unity2.0 ทำให้นายเอิธ แก้ไขโปรแกรมได้โดยไม่ส่งผลกระทบกับการทำงานในส่วนอื่นยังไงน่ะครับ อยากรู้ ครับ จัดให้หน่อยนะครับ
REPLY #29 (129539)
#27
เป็นตอนต่อไปครับเมื่อได้ใช้งานร่วมกับ asp.net mvc 3 จะมีตัวอย่างรอหน่อยเด้อค้าบ
#28
จริงๆแล้วไม่ต้องแอด referenceครับ เอาออกได้เลย แต่ที่แอดไปเพราะต้องการให้ dll ทั้งสอง project ไปวางใน bin ของ WebUI เท่านั้้นเอง
REPLY #30 (129696)
อยากทราบว่าการออกแบบด้วย Dependency Injection นี้ใน Domain Class สามารถมี Method ที่ใช้ในการทำงานเป็นของตัวเองได้ไหมครับ หรือว่าพวก Method ต่างๆ ต้องอยู่ใน BLL (คลาสที่เป็น Service) เท่านั้นครับ ขอบคุณครับ
REPLY #35 (130312)
คุณนายครับ ถ้าจะใช้ POCO Entity แทน Code First ใน EF4 ที่ยังไม่มี Code First Feature เราสามารถนำ POCO Entity ไปไว้ที่ Domain และ ObjectContext อยู่ที่ Data Layer ได้หรือไม่ครับ ถ้าได้จะมีผลกระทบกับการใช้ Code First Feature ในอนาคตหรือไม่ครับ ขอบคุณครับ
REPLY #37 (130398)
คุณสามารถใช้ t4 ตัวที่ชื่อ ADO.NET Self-Tracking Entity Generator ทำการ gen poco ออกมาครับ
เสร็จแล้ว ไปที ่project ให้ right click ที่ *__Contexts.Types.tt เลือก run custom tool
จากนั้นก็ move *__.Types.tt + (*.cs ทุกตัวที่อยู่ใน + ) ไปยัง domain project
REPLY #38 (130407)
ผมไม่ชอบวิธีการสร้าง poco ของ EF4 ครับ
เนื่องจากต้องใช้ template ไป extract เอา model ออกมาจาก EDM ทำให้ขั้นตอนวุ่นวาย และอาจจะพลาดเอาได้ง่าย ๆ กรณีลืมเอา customtool ออกจาก .tt file
แต่สำหรับ EF 4.1 code first ทำได้ดีครับ ดูเรียบง่ายมาก แต่ก็ยังติดในเรื่องของความสามารถในหลายเรื่อง คาดว่าจะปรับปรุงใน EF5 คงไม่เกิน สิ้นปีนี้คงได้ยลกัน
ไม่มีความคิดเห็น:
แสดงความคิดเห็น