Archive
View State , ASP.NET State Management
HTTP นั้นเป็น protocol ประเภทที่เรียกว่า stateless ขึ้น มาแบบนี้ programmer ที่ทำงานด้านนี้ทราบดีอยู่แล้ว แต่สำหรับมือใหม่ หรือเพิ่งเข้ามาจับงานประเภท web base programming ละก็จะต้องทำความเข้าใจกันซะก่อนเป็นเบื่องต้น และเข้าใจมาก ๆ ในเบื้องกลาง และเข้าใจสุด ๆ ในเบื้องปลาย ฮะ ฮะ
Stateless เป็นอย่างไรนะหรือครับลักษณะก็คือ ทุกครั้งที่มีการ request ไปยัง server ( หมายถึง webbrowser นะครับ ) เสร็จแล้วก็จะ disconnect เลยนะครับไม่มีการ คง connection ไว้ เพราะฉะนั้น จะไม่มีการคงอะไรไว้หรือรักษาอะไรไว้เลยระหว่าง สอง connection แบบนี้แหละครับที่เรียกว่า stateless เหตุผลของ architecture นี้ก็คือ เพื่อให้ server สามารถรองรับการ request พร้อม ๆ กัน หลาย ๆ พัน หลาย ๆ หมื่น client โดยที่ไม่เกิด run out of memory นะครับ แต่ปัญหา ของมันก็คือ อืม เราไม่เรียกว่าปัญหา เรียกว่าข้อด้อยของมันดีกว่า ข้อด้อยของมันก็คือ programmer จะต้องหา techniques อื่นเพื่อรักษาข้อมูลระหว่างที่มีการ request และสามารถดึงข้อมูลเหล่านั้นมาใช้งานได้ตามต้องการนั่นเอง
ที่เกรินมาทั้งหมดนี้ก็ เพราะว่า ผมจะหาข้อมูลและเขียนเกี่ยวกับ State Management ในหลาย ๆ ระดับ มาเล่าสู่กันฟัง และก็จะรวมถึง technique การส่งผ่านส่งผ่านข้อมูลจาก page หนึ่งไปยังอีก page ด้วย ดังนั้นในหัวข้อเหล่านี้มันก็คง จะมีหลาย ๆ ตอนอยู่นะครับ ถ้าพลังมีมาก ผมคง ให้รายละเอียดได้ทั้งหมด
View State
View state เห็นจะเป็นหัวข้อแรกที่ผมเลือก ที่จะพูดถึงเพราะ View state นี้เราใช้ในการรักษาข้อมูล ภายใต้กรอบการทำงาน page เดียวเท่านั้น ( ASP.NET web controls ใช้ View State เป็นพื้นฐานอยู่แล้ว ) ซึ่ง View state มันจะทำให้เรารักษาข้อมูลระหว่าง postbacks ได้ ( เหตุการณ์ที่จะทำให้เกิด postback เช่น การ click ปุ่มเป็นต้น )
Page object มี property ที่ชื่อว่า ViewState เป็นตัวช่วยให้เราสามารถที่จะเพิ่มข้อมูลเข้าไปใน View State Collection ได้ และ ข้อมูลที่เราสามารถที่จะเก็บลงใน view state ก็เป็นได้ทั้ง simple data types และ ก็ objects ที่เราสร้างขึ้นมา
ต่อไปเรามาดูการใช้งาน View State กันนะครับ เนื่อง จาก View State นั้นมีรูแบบ ที่เรียกว่า dictionary collection ก็หมายถึงว่าข้อมูลแต่ละตัวจะถูกอ้างอิงด้วย string name ที่แตกต่างกัน เช่น
ViewState[“Counter”] = 1;
นั่นเป็นการ เก็บ 1 ลงใน ViewState Collection และอ้าอิงด้วยชื่อที่มีความหมาย คือ Counter ตรงนี้สำคัญนะครับ ถ้า Counter ยังไม่เคยปรากฏอยู่ใน ViewState collection เลย มันก็จะเพิ่ม Counter เข้าไปและเก็บค่า 1 แต่ถ้า Counter มีอยู่แล้ว มันจะแทนที่ค่าเดิมด้วย 1 นะครับ
การ retrieve ข้อมูล เราก็อ้าอิง ด้วยชื่อ Counter ในลักษณะเดียวกับการ บันทึกข้อมูลงไป ( Counter เราเรียกว่าเป็น Key name ) และเราก็ต้องทำ casting ด้วย data type ที่เหมาะสมนะครับ เพราะการเก็บค่าเข้าเป็นใน View state collection นั้นมักจะถูก ทำให้เป็น object โดยอัตโนมัติ ( กรณีที่พวก simple data type เช่น integer ) ดังนั้นต้อง casting ก่อนเพื่อให้ได้ข้อมูลที่ถูกต้องและนำไปใช้งาน ต่อไปเป็นตัวอย่างของ code แสดงการ retrieve ข้อมูลจาก View State collection
int counter ;
if (ViewState[“Counter”] != null)
{
Counter (int)ViewState[“Counter”];
}
ถ้าเรา พยายามที่จะ retrieve ข้อมูลซึ่งไม่มีเก็บอยู่ใน หรือ ไม่ปรากฏอยู่ ใน Collection เราจะได้ NullReferenceException มาแทน ใน code ก็เลยต้องทำการ ตรวจสอบซะก่อนว่า มีข้อมูลปรากฏอยู่หรือไม่ ถือว่าเป็นหลักปฏิบัติโดยทั่วไป นะครับ
จากตัวอย่างที่ผ่านมา เราสามารถประยุกค์ใช้กับ Data types ชนิดอื่นได้ (Simple data types) นอกจากนี้เรายังสามารถที่จะบันทึก ข้อมูลที่เป็น object ลงใน Veiw State ได้ด้วยเช่นกัน แต่การบันทึกข้อมูล objects ลง View State นั้น Object จะต้องเป็น Object ที่ประกาศให้สามารถ แปลงให้เป็น bytes stream ได้ หรือ ที่เรียกว่า serialization ครับ การทำก็ไม่ได้เป็นเรื่องยากอะไรครับ แค่เพิ่ม ‘Serializable’ attribute ไว้ที่ การประกาศ class ดังตัวอย่าง
[Serializable]
public class Student
{
public string firstName;
public string lastName;
public Student(string fName, string lName)
{
firstName = fName;
lastName = lName;
}
}
การเก็บค่าลงใน View State
// Storing a student in view state.
Student stud = new Student(“John”, “Doe”);
ViewState[“CurrentStudent”] = stud;
และการดึงข้อมูลออกมาใช้งาน
// Retrieve a student from view state.
Student stud = (Student) ViewState[“CurrentCustomer”];
อย่าลืมนะครับ เวลาเราดึงข้อมูลจาก View Sate จะต้องทำการ casting ให้เป็น Datatype เดียวกับที่เรา ใส่ลงไปนะครับ ในเรื่อง serializable นั้น มีข้อกำหนดดังนี้นะครับ
- class จะต้องกำกับด้วย serializable attribute
- derive ก็จะต้อง เป็น serializable ด้วย
- ตัวแปร private ของ class ก็จะต้องเป็น serializable หรือ ถ้ามี none serializable ก็จะต้องกำกับ ด้วย [none serializable] attribute
ตัวอย่างการใช้งาน สมมติว่าเราจะทำ PostBack counter ก็คือเราจะทำการเพิ่มค่าทีละ 1 ทุกครั้งที่มีการ click ปุ่มนะครับ ลองมาดูกันว่าเป็นอย่างไร
อันดับแรกเรา สร้าง page ใส่ control ดังต่อไปนี้
และที่ code เป็นอย่างนี้ครับ อันดับแรกเป็นของ Page load event
protected void Page_Load(object sender, System.EventArgs e)
{
//First we check if the page is loaded for the first time,
if (!IsPostBack)
{
ViewState[“PostBackCounter”] = 0;
}
//Now, we assign the variable value in the text box
int counter = (int)ViewState[“PostBackCounter”];
Label1.Text = counter.ToString();
}
ถัดมาเป็น ของ OnClick ของ Button1
protected void Button1_Click(object sender, System.EventArgs e)
{
int oldCount = (int)ViewState[“PostBackCounter”];
int newCount = oldCount + 1;
//First, we assign this new value to the label
Label1.Text = newCount.ToString();
//Secondly, we replace the old value of the view state so that the new value is read the next //time
ViewState[“PostBackCounter”] = newCount;
}
ครับ จาก code จะเห็นว่า ทุกครั้งที่มีการ Click ปุ่ม จะทำการเพิ่มค่าให้ PostBackCounter ทีละหนึ่งนะครับ แต่ครั้งแรกที่ load page นี้จะทำการ set ค่าไปที่ 0
ครับที่กล่าวมาและตัวอย่าง ต่าง ก็คงพอที่จะได้ แนวความคิดการใช้งาน View state บ้าง ไม่มากก็น้อยนะครับ
การป้องกันการโจมตีด้วย SQL Injection , SQL Injection Attacks
ในเรื่องที่จะพูดถึงนี้ น่าจะพูดได้ว่าเป็นเกร็ดเล็กเกร็ดน้อยที่ ผู้พัฒนาควรจะรู้และเป็นประโยชน์มากเมื่อนำไปประยุกต์ใช้งาน เรื่องที่จะพูดถึงก็คือ การป้องกันการโจมตีด้วย
SQL Injection มันเป็นอย่างไร พูดอย่างง่าย ๆ ก็คือ กระบวนการหรือการ ส่งหรือใส่ SQL code ไปยัง application โดยที่ผู้พัฒนาไม่ได้ตั้งใจหรือไม่ได้เจตนาที่ทำให้เกิด SQL code แบบนั้น ( เกิดจากบุคคลที่ 3 ด้วยความปรารถนาที่เป็นลบนั่นเอง ) คงพอจะเข้าใจนะครับ ซึ่งเหตุการณ์แบบนี้จะเกิดขึ้นได้ก็ต่อเมื่อ application นั้นถูกออกแบบมาอย่างไม่ดีเท่าที่ควร หรือที่เรียกว่า poor design นะครับ — ส่วนใหญ่มันมักถูกจะออกแบบมาแบบนั้นซะด้วยซิ ว่าไหมครับ — ซึ่งมันก็จะเกิดกับ application ที่ใช้เทคนิค การสร้าง SQL String ให้กับ Command Object โดยการรับข้อมูลมาจาก ผู้ใช้งาน คงจะนึกภาพไม่ออก มาดูตัวอย่างเพื่อให้มองเห็นภาพอย่างชัดเจนดีกว่านะครับ
String sql =
“SELECT Orders.CustomerID, Orders.OrderID, COUNT(UnitPrice) AS Items, “ +
“SUM(UnitPrice * Quantity) AS Total FROM Order “ +
“INNER JOIN OrderDetails “ +
“ON Orders.OrderID = OrderDetail.OrderID “ +
“WHERE Orders.CustomerID = ‘” + txtID.Text +” ’ ” +
“GROUP BY Orders.OrderID, Orders.CustomerID”;
SqlCommand cmd = new SqlCommand(sql,connection);
จากตัวอย่าง ผู้บุกรุกอาจจะทดสอบ SQL statement ด้วยวิธีการต่าง ๆ โดยจุดประสงค์แรกก็คือเพื่อให้ได้มาซึ่ง error Message ซึ้งถ้า Error นั้นไม่ถูกปล่อยปะละเลยหรือไม่ถูกดำเนินอย่างเหมาะสมแล้วละก็ System Error message อาจถูกแสดงออกไป และเป็นประโยชน์ต่อผู้บุกรุกที่จะนำไปใช้ต่อไปได้อีก
ตัวอย่าง ลองคิดกันดูนะครับว่าจะเกิดอะไรขึ้นถ้า user ป้อนข้อมูลนี้ลงใน TextBox จากตัวอย่างข้างต้น
ALFKI’ OR ‘1’=’1
จะส่งผลให้ SQL Statement เป็นดังนี้
SELECT Orders.CustomerID, Orders.OrderID, COUNT(UnitPrice) AS Items,
SUM(UnitPrice * Quantity) AS Total FROM Orders
INNER JOIN [Order Details]
ON Orders.OrderID = [Order Details].OrderID
WHERE Orders.CustomerID = ‘ALFKI’ OR ‘1’=’1′
GROUP BY Orders.OrderID, Orders.CustomerID
พิจารณา Expression หลัง WHERE จะเห็นว่า ข้อมูลทั้งหมดในตาราง Orders จะถูกแสดงออกมา ใช่ไหมครับ ผมคงไม่ต้องอธิิบายรายละเอียดนะครับ เนื่องจากว่า มันเป็น Logic ของ OR ใช่ไหมครับ ถ้าค่าด้านหนึ่งด้านใด เป็น จริง ก็จะส่งผลให้ทั้ง Statement เป็น จริงแมนบ่ ครับ น่านแหละครับ 1=1 เป็น จริง ไม่ว่า CustomerID จะเป็นอะไรก็ตาม ทั้งหมดเป็นจริง
ครับ คิดต่ออีกนิดถ้าหากว่าข้อมูลดังกล่าวนั้นเป็นข้อมูลที่ Sensitive หละครับ เช่น Social Security numbers วันเกิด หรือ ข้อมูล Credit card ละก็จะถือว่านี้เป็นปัญหาใหญ่หลวง ซึ่งอาจก่อให้เกิดความเสียหายมหาศาล ใช่ใหม่ครับ
ตัวอย่างวิธีการที่ซับซ้อนมากกว่านั้นขึ้นไปอีกเช่น
ผู้บุกรุกสามารถ comment ส่วนท้ายของ SQL Statement โดยใช้ — สำหรับ SQL Server ( MySql ใช้ # และ Oracle ใช้ ; ) หรือ ใช้ การสั่งงานแบบ Batch ได้เพื่อเพิ่ม SQL Code เข้าไปทำงานอย่างใดอย่างหนึ่ง ในกรณีนี้ สำหรับ SQL Server ก็แค่เพียงเพิ่ม semicolon และตามด้วย Command ที่ีต้องการ ซึ่งทำให้ผู้บุกรุกสามารถ ลบข้อมูลใน Table อื่นได้ หรือ สามารถเรียกใช้ SQL Server xp_cmdshell system stored procedure เพื่อประมวลผลโปรแกรม ที่ command line ได้ เป็นไงหล่ะครับ ไปได้ถึงขนาดนั้น
ครับลองดูตัวอย่างนี้นะครับ ถ้าผู้ใช้งานกรอกข้อมูลนี้ลงใน txtID TextBox ลองพิจารณาดูนะครับ
ALFKI’ ; DELETE * FROM Customers —
หมายความว่า หลังจากที่ได้แสดงข้อมูล Orders ของ CustomerID เท่ากับ ALFKI แล้ว ต่อด้วยการลบข้อมูลทั้งหมดในตาราง Customers และยกเลิก คำสั่งต่อท้ายที่เหลือทั้งหมด ครับ
ว่ากันด้วยการป้องกัน —
ต่อมา เราลองมาดูกันว่าเราจะทำอย่างไรถึงจะสามารถจัดการหรือป้องกันการโจมตีแบบนี้ ได้บ้าง
แน่นอนครับ สำหรับโปรแกรมเมอร์แล้วเราสามารถการปฏิบัติตาม แนวทางที่เราเชื่อว่าเป็นแนวทางการปฏิบัติที่ดีเช่น การจำกัดความยาวของข้อมูลที่อนุญาตให้ป้อนลงใน TextBox โดยการกำหนด TextBox.MaxLength ไม่ให้ยาวเกินความจำเป็น ซึ่งเป็นการลดโอกาสที่ ผู้รุกจำสามารถป้อน script ยาว ๆ ลงไปได้ ยิ่งไปกว่านั้น เราต้องทำการจำกัดข้อมูลของ Error message ถ้าเราดัก exception ของ database เราก็ควรที่จะแสดงเฉพาะข้อความที่เป็นคำอธิบาย เช่น “Data source error” แทนที่จะแสดง ข้อมูลใน Exception.Message ซึ่งอาจจะเป็นจะเป็นการชี้ โพรงให้กระรอกได้ ( ช่องโหว่ของระบบได้ )
และยิ่งไปกว่านั้น คุณก็ต้อง จัดการกับ character พิเศษต่าง เช่น แปลง single quotation marks (‘) เป็น two quotation marks (“) เพื่อให้ไม่ทำให้เกิดความสับสนกับ ตัวปิดหัวท้าย
string ID = txtID.Text().Replace(“‘”,”””);
ครับ ไม่ได้หมายถึงมันจะจบสิ้นนะครับ มันจะนำมาซึ่ง ความปวดหัวเพิ่มเข้าไปอีกถ้า ค่าของ txtID.Text นั้นมี apostrophes ขึ้นมา ซึ่งมันอาจจะเกิดขึ้นได้ อีกต่อ ๆ ไปซึ่งสรุปได้ว่า แนวทางปฏิบัติดังกล่าวไม่สามารถแก้ไขปัญหา SQL Injection ได้อย่างสมบูรณ์แบบ ผมสรุปแบบนี้ไม่ใช่ว่ามันจะจบลงแบบนี้หรอกนะครับ เพียงแต่จะบอกว่ามีวิธีการที่ดีกว่านั้นสำหรับการป้องกัน SQL injection attack ซึ่งผมจะไ้ด้กล่าวต่อไป
การป้องกัน SQL injection attack โดยการ ใช้ Parameterized Commands
Parameterized Command ใช้ placeholders ใน SQL text ซึ่ง Parameterized command นี้เป็นตัวกำหนด dynamic values ที่จะส่งผ่านมาทาง Parameters collection ของ Command object ดูดังตัวอย่าง นะครับ
จาก SQL Statement
SELECT * FROM Customers WHERE CustomerID = ‘ALFKI’
เปลี่ยนเป็น
SELECT * FROM Customers WHERE CustomerID = @CustID
ลองมาดูตัวอย่างเต็ม ๆ กันนะครับ ( สมมติว่าเราสร้าง connection object ไว้แล้ว )
String sql =
“ SELECT Orders.CustomerID, Orders.OrderID, COUNT(UnitPrice) AS Items, “ +
“ SUM(UnitPrice * Quantity) AS Total FROM Orders “ +
“ INNER JOIN OrderDetails “ +
“ ON Order.OrderID = OrderDetails.OrderID “ +
“ WHERE Orders.CustomerID = @CustID “ +
“ GROUP BY Orders.OrderID, Orders.CustomerID” ;
SqlCommand cmd = new SqlCommand(sql,con);
cmd.Parameters.Add(“@CustID”, txtID.Text);
ถ้าเราลองทำ SQL injection attack กับ code ที่ถูกปรับปรุงใหม่แล้ว เราจะบบว่าโปรแกรมจะไม่ส่งข้อมูลใดออกมาแสดงผลเลยเนื่องจาก นั่นก็หมายความว่าไม่มี order items ใดที่มีข้อมูล customerID มีค่าเท่ากับ ALFKI’ OR ‘1’=’1 ซึ่งเป็นสิ่งที่คุณต้องการใช่ไหมครับ
การป้องกัน SQL injection attack โดยการ ใช้ Stored Procedures
Stored Procedures ผมคงจะไม่อธิบายว่ามันคืออะไรทำงานอย่างไรหรอกนะครับ คิดว่าท่านทั้งหลาย รู้จักดีอยู่แล้ว แต่จะขอกล่าวถึงประโยชน์ของมันสักนิดหนึ่ง คือ
- ง่ายต่อการบำรุงรักษา หมายถึงว่าตัว stored procedures นั้นมันแยกอยู่ต่างหากกับโปรแกรมที่เรียกใช้มัน เพราะฉะนั้นหากจะต้องมีการเปลี่ยนแปลงอะไรที่ stored procedure ก็สามารถทำได้โดยไม่ต้องมีการ Recompile ทั้งโปรแกรมนั่นเอง
- ทำใ้ห้เราสามารถใช้งาน database ในวิถีทางที่ปลอดภัยได้ เราสามารถที่จะกำหนดให้ stored procedures สามารถเข้าถึงเฉพาะ tables ที่กำหนดได้
- – ทำให้ประสิทธิภาพการทำงานดีขึ้น เนื่องจาก stored procedures สามารถรวม หลาย ๆ คำสั่งทำงานในคราวเดียวกันได้ ซึ่งทำให้เราสามารถทำงาน ให้เสร็จหลาย ๆงานพร้อมกันได้ต่อหนึ่งรอบการเข้าถึง database
มาดูตัวอย่างกันดีกว่านะครับว่าจะใช้งานอย่างไร ซึ่งจริงแล้วไม่ก็คือ การใช้ Place holder กับ Parameterized Command Object นั่นแหละ ครับ เพียงแต่เราเรียกใช้ stored procedure แทนที่จะใช้การส่งผ่าน sql text สมมติว่าเราสร้าง Stored procedure ไว้ดังนี้นะครับ
CREATE PROCEDURE InsertEmployee
@TitleOfCourtesy varchar(25),
@LastName varchar(20),
@FiratName varchar(10),
@EmployeeID int OUTPUT
AS
INSERT INTO Employees ( TitleOfCourtesy, LastName, FirstName, HireDate )
VALUES (@TitleOfCourtesy, @LastName, @FirstName, GETDATE());
SET @EmployeeID = @@IDENTITY
GO
Stored procedures มี parameters 3 ตัวคือ TitleOfCourtesy, LastName, และ FirstName ซึ่ง return ID ของ record ออกมาทาง output parameters ชื่อว่า @EmployeeID
ต่อไปเราสร้าง SqlCommand เพื่อเรียกไปยัง stored procedure command นี้มี input Parameters 3 ตัวเช่นเดียวกัน และ ส่งค่า ID ของ record ใหม่ออกมาให้ต่อไปเป็นตัวอย่างของการสร้าง
SqlCommand cmd = new SqlCommand(“InsertEmployee”, con);
cmd.CommandType = CommandType.StoredProcedure;
ต่อไปเราก็เพิ่ม parameters ของ stored procedures เข้าไปที่ Collection ของ Command.Parameters ซึ่งเราต้องกำหนด data type และ ขนาดของ parameter นั้นด้วย ดังนี้
cmd.parameters.Add(new SqlParameter(“@TitleOfCourtesy”, SqlDbType.NVarChar,25));
cmd.Parameters[“@TitleOfCourtesy”].Value = title;
cmd.Parameters.Add(new SqlParameter(“@LastName”, SqlDbType.NVarChar, 20));
cmd.Parameters[“@LastName”].Value = lastName;
cmd.Parameters.Add(new SqlParameter(“@FirstName”, SqlDbType.NVarChar, 10));
cmd.Parameters[“@FirstName”].Value = firstName;
cmd.Parameters.Add(new SqlParameter(“@EmployeeID”, SqlDbType.Int, 4));
cmd.Parameters[“@EmployeeID”].Direction = ParameterDirection.Output;
สังเกตว่า Parameter ตัวสุดท้ายนั้นเป็น output parameter นะครับ
สุดท้ายเราก็ทำการ run
con.Open();
try
{
int numAff = cmd.ExecuteNonQuery();
HtmlContent.Text += String.Format(“Inserted {0} record(s)
“, numAff);
// Get the newly generated ID.
empID = (int)cmd.Parameters[“@EmployeeID”].Value;
HtmlContent.Text += “New ID: ” + empID.ToString();
}
finally
{
con.Close();
}
ครับทั้งหมด ก็มาจบตรงที่ สองวิธีการสุดท้ายที่เราสามารถที่จะป้องกันการ โจมตี ด้วย SQL Injection ได้อย่างมีประสิทธิภาพ ก็คือการใช้ Parameterized Commands และการใช้ Stored procedures นั่นเอง นี่เป็น ส่วนหนึ่งของการแก้ปัญหานี้นะครับ
หากต้องการข้อมูลเพิ่มเติมละก็ ผมใช้ข้อมูลจากหนังสือเล่มนี้นะครับ Pro ASP.NET 2.0 in C# 2005
การทำ Master-Detail โดยใช้ BindingSource,Master-Detail using BindingSource
ในการสร้าง Database Application นั่นในบางครั้งเราอาจจะต้องการ การแสดงผลในลักษณะที่เป็น Master-Detail คือมีตารางหลักและตารางที่แสดงผล Detail อีกหนึ่งตาราง เมื่อคลิกที่ record ใด record หนึ่งในตารางหลัก ก็จะนำไปสู่การแสดงผลที่ตารง Detail ที่สัมพันธ์กับข้อมูลในตารางหลัก ซึ่งการทำในลักษณะนี้เราเรียกกันว่า Master-Detail นะครับ ส่วนวิธีการนั้นก็มีวิธีการทำได้หลากหลายนะครับตั้งแต่ แบบลูกทุ่ง ไปจนถึง Best Practices ในบทความนี่เราจะหล่าวถึงวิธีที่การทำโดยใช้ component ที่ชื่อว่า BindingSource ถ้าใครเป็นแฟนของ ADO.NET ก็คงพอจะรู้ว่า มันเป็น component ที่เริ่มมีใช้ใน version 2.0 นะครับ ถ้าผมไม่เลอะเลือน ซึ่งวิธีการนี้จะทำให้ programmer ลดงานต่าง ๆ ลงมากและ ลดข้อผิดพลาดลงได้มากนะครับ ไม่ต้องเสียเวลาไปพะวงกับการ ตรวจสอบมากจนเกินไป
สำหรับการทำงานผมจะกล่าวถึง concept ในการทำก่อน นะครับ ตัวอย่างของแนวคิดนี้ก็เช่น เรามีตาราง Orders และ OrderDetails โดยทั้งสองตารางนี้เราอ้างอิงกันโดยใช้ OrderID นะครับ ดังรูป

แล้วใน windows Form นั้นเราต้องการแสดงผลดังนี้

นั่นก็คือเราใช้ DataGridView กับทั้งสอง table แล้ว Binding ด้วย BindingSource ให้กับทั้งสอง table ซึ่งเดี๋ยวเราจะมาดูกันว่าเราจะ Binding อย่างไร และเราก็จะต้อง สร้าง Relation ของทั้งสองตารางนี้ ใน DataSet ที่เรา Fill ทั้งสองตารางนี้ลงไปด้วย คือเราจะทำการ Fill ทั้งสอง Table ลงใน Dataset เดียวกันแล้วก็สร้าง Relation ระหว่างทั้งสอง Tables นี้แล้วจึง ทำการ Binding ให้กับ DataGridView นะครับ นั่นคือ Concept ทั้งหมดในการทำ ต่อไปเรามาดูขั้นตอนวิธีการทำเลยดีกว่านะครับ
สมมุติว่าเราได้สร้าง windows Form แล้วและเราได้ นำเอา DataGridView มาวางใน Form แล้วตั้งชื่อให้ว่าเป็น dgvOrders และ dgvOrderDetails นะครับ อีกนิดหนึ่งนะครับ ฐานข้อมูลถ้าเราใช้ Microsoft Access เราต้อง using System.Data.OleDb หรือ ถ้าเราใช้ Microsoft SQLServer เราต้อง using System.Data.SqlClient นะครับ
- เราก็จะต้องประการตัวแปร object ต่าง ที่เราจะต้องใช้งานเสียก่อนนะครับ เช่น OleDbConnection, OleDbCommand, OleDbDataAdapter, BindingSource
- ต่อไป เราจะทำการ Binding ใน Form1_load นะครับ ในที่นี้ผมจะไม่กล่าวถึง Connection String นะครับ ทุกท่านคงจะรู้แล้วนะครับ เราจะกำหนด Sql String ไว้สองชุดเพื่อการเข้าถึงข้อมูลในแต่ละ Table จะใช้ Sql String อันเดียวก็ได้นะครับ ผมใช้สองชุดก็เพื่อให้การอธิบายมันชัดเจน แล้วเราจะใช้ myCommand กำหนด SelectCommand ให้กับ myDataAdapter และ เราก็จะ Fill ลงใน DataSet ซึ่งเรา จะทำการ Fill ลงไปสองครั้ง ดูตามตัวอยางก็แล้วกันนะครับ private void Form1_Load(object sender, EventArgs e)
{
string ConnectionString = GetConnectionString();
myConnection = new OleDbConnection(ConnectionString);
myConnection.Open();string SqlString1 = “Select * from Orders”;
string SqlString2 = “Select * from OrderDetails”;myCommand = myConnection.CreateCommand();
myCommand.CommandType = CommandType.Text;
myCommand.CommandText = SqlString1;myDataAdapter = new OleDbDataAdapter();
myDataAdapter.SelectCommand = myCommand;myDataSet = new DataSet();
myDataAdapter.Fill(myDataSet, “Orders”);
myCommand.CommandText = SqlString2;
myDataAdapter.Fill(myDataSet, “OrderDetails”);
}
- ครับต่อไปเราจะสร้าง Relation ระหว่าง 2 Tables นี้นะครับ เราจะตั้งชื่อ Relation นี้ว่า เป็น Oreder_OrderDetail นะครับ และเราก็จะใช้ OrderID เป็น ตัวข้อมูลอ้างอิงของทั้งสองตารางดังนี้นะครับดูตามตัวอย่างเลย นะครับ private void Form1_Load(object sender, EventArgs e)
{
.
.
.
myDataAdapter.Fill(myDataSet, “Orders”);myCommand.CommandText = SqlString2
myDataAdapter.Fill(myDataSet, “OrderDetails”);
// —- สร้าง relation —–
DataRelation myDataRelation = new DataRelation(“Order_OrderDetails”,
myDataSet.Tables[“Orders”].Columns[“OrderID”],
myDataSet.Tables[“OrderDetails”].Columns[“OrderID”]);
//————————
orders_BindingSource = new BindingSource();
ordersDetail_BindingSource = new BindingSource();// –กำหนด DataSource ให้กับ Bindingsource
orders_BindingSource.DataSource = myDataSet;
orders_BindingSource.DataMember = “Orders”;ordersDetail_BindingSource.DataSource = orders_BindingSource;
ordersDetail_BindingSource.DataMember = “Order_OrderDetails”;// –Binding ให้กับ Datagridview ทั้งสอง
dgvOrders.DataSource = orders_BindingSource;
dgvOrderDetails.DataSource = ordersDetail_BindingSource;
} - ต่อไปทำ การสร้าง BindingSource และกำหนด DataSource ให้กับ BindingSource ทั้งสองนะครับ และก็ต่อไปเลยก็คือ Binding ให้กับ DataGridView ทั้งสองด้วย ดูต่อจาก code ด้านบนนะครับ…
- ครับ ถ้าดูจากตัวอย่าง code แล้วคงจะพอเข้าใจได้ไม่ยากนะครับ ทั้งหมดนี้ก็เป็นการ ทำการ Binding ในลักษณะ Master-Details ซึ่งผมเคยลองหาใน internet แล้วก็ค่อนข้างที่หายากนิดหนึ่งไม่ค่อยมีใครอธิบายละเอียดละออเป็นภาษาไทยนะครับ ผมก็พยายามอธิบายให้ละเอียดเพื่อเพื่อน โปรแกรมเมอร์คนไทยด้วยกัน ในเรื่องของการ Binding แบบนี้ นะครับ เป็นคุณลักษณะที่ ใช้กับ ADO.NET 2.0 ขึ้นไปนะครับ รู้สึกว่าถ้าต่ำกว่านี้เขาจะไม่มี BindingSource ครับ จริงแล้วการ Binding นั้นยังมีอีกมากนะครับ ในตัวอย่างที่ผมกล่าวมาแล้วนี้เป็นลักษณะ one to many นะครับ ยังมีแบบ many to many อีก ซึ่งคงต้องเอาไว้มีเวลาแล้วจะมาลงไว้ให้นะครับ หรือถ้าใครมีแหล่งข้อมูลก็ช่วย post comment ให้ด้วยนะครับเพื่อ เป็นวิทยาทาน ให้กับเพื่อนโปรแกรมเมอร์ด้วยกัน นะครับ s_teerapong2000@yahoo.com
- สำหรับหนังสือที่เรียกได้ว่าเป็นสุดยอดของเรื่องนี้ก็ต้องเล่มนี้นะครับ “Data Binding with Windows Forms 2.0 – Programming Smart Client Data Applications with .NET” นะครับ
.
.
using System.Data.OleDb;
namespace WindowsApplication1
{
public partial class Form1 : Form
{
OleDbConnection myConnection;
OleDbCommand myCommand;
OleDbDataAdapter myDataAdapter;
BindingSource orders_BindingSource;
BindingSource ordersDetail_BindingSource;
DataSet myDataset;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
.
.
.
}
การทำ Deploy .NET Window application
พอดีจะทำ Setup project กับ โปรเจคเล็ก ๆ ทีทำไว้ ต้องการให้รวมเอา Ms Access ไปด้วยและต้อง รวมเอา Crystal Report Runtime เข้าไปด้วย ยังหาข้อมูลตรงนี้ไม่ได้ ก็เลยตั้งใจว่าคงต้อง ให้เวลากับมันสัีกนิด เพื่อทำความเข้าใจจริงจัง แล้วไหน ๆ ทำแล้วก็ เผยแพร่เพื่อ เพื่อน ๆ โปรแกรมเมอร์ และนักพัฒนาโปรแกรม ด้วยกัน …
ไหน ๆ ก็ พูดแล้วกล่าวถึงคำว่า Deployment กันสักนิด หลังจากที่เราได้พัฒนา Application เส็จสิ้นสมบูรณ์แล้ว ถึงเวลาที่เราจะนำโปรแกรมไปให้กับผู้ใช้งาน หรือสร้างเป็นชุด Installation เพื่อทำให้โปรแกรมของเราสามารถที่ทำงานบนเครื่องผู้ใช้ได้อย่างสมบูรณ์ เราก็ต้องสร้าง ชุด Install ที่สามารถนำไฟล์ทุกไฟล์ที่เกี่ยวข้อง กับการทำงานของโปรแกรม ไม่ว่าจะเป็น .exe , .dll , component ต่าง ๆ ที่เกี่ยวข้อง third party component ไฟล์อื่น ๆ เป็นต้น หรือแม้แต่การสร้าง directory การสร้าง shortcut การทำ environment บนเครื่องเป้าหมาย ให้มีสภาวะเช่นเดียวกับ เครื่องที่เราใช้พัฒนา กระบวนการที่เราทำสิ่งเหล่านี้ก็คือการ Deployment นั่นเอง
วิธีการที่เราจะกล่าวถึง สำหรับการ Deploying .NET Application คือการใช้ Visual studio .NET Deployment Tools ซึ่งใช้เครื่องมือที่มีมาพร้อมกับ .NET Visual studio ในบทความนี้ เราอ้างอิง Visual Studio 2005 นะครับ ซึ่งเจ้าเครื่องมือนี้ เราสามารถใช้สำหรับการทำงานดังต่อไปนี้นะครับ
- ใช้ Copy ไฟล์ที่จำเป็นทั้งหมดไปยังเครื่อง เป้าหมาย
- นอกจากที่มันทำในข้อ 1 แล้วมันก็ยังสามารถเอาไฟล์เหล่านั้นไปไว้ยัง Folders ต่าง ๆ ตามความต้องการของโปรแกรม
- และเรายังสามารถใช้ให้มันสร้าง Registry entry ได้ตามต้องการอีกด้วย
- และยิ่งไปกว่านั้นเราสามรถทำ custom Dialog ได้ และตรวจสอบ ความต้องการเบื่องต้น เช่น ตรวจสอบว่ามี .NET Framework บนเครื่องเป้าหมายหรือไม่ เป็นต้น
- และอื่น ๆ อีก เราสามารถเลือกใช้ได้ตามความต้องการของเรานะครับ
เรามาดูกันที่ เมื่อเริ่มต้นสร้างเราสร้างกัน อย่างไร การเริ่มต้นสร้าง นั้นใช้วิธีการเดียวกับการสร้างโปรเจค นะครับ กล่าวคือเมื่อเราเริ่มสร้างโปรเจค เราเลือก Project type ที่ชื่อว่า Setup Deployment project เราสามารถเลือก template สำหรับการ deployment ได้ตามชนิดของ Application ที่เราทำนะครับ ซึ่งมันก็จะมี
- Setup Project สำหรับการสร้าง Deployment ให้กับ Project ธรรมดาที่ทำงานโดยใช้ windows form
- Web Setup Project สำหรับสร้าง Deployment ให้กับ Project ที่เป็น Web application
- Merge module project สำหรับ การสร้าง Deployment ให้กับ component หมายถึงเราจะ deploy component ที่เราสร้างนะครับไม่ใช่ application
- Cab project ก็เช่นเดียวกัน นะครับสำหรับการ deploying component เช่น component ที่สร้างสำหรับการทำงานบนเครื่อง client เราใช้ component ชนิดนี้วางไว้บน Web server เมื่อ Web browser ต้องการ component การสามารถ download จาก server และทำที่เครื่อง client ได้ คงจะพอนึก app. ประเภทนี้ออกนะครับ
- Web Wizard Project มี Wizard นำทางตลอดทุกขั้นตอนในการทำงาน เหมาะสำหรับ มือใหม่นะครับ จะเริ่มที่นี่
- Smart Device Cab Project สำหรับการ Deploying Application ที่ทำงานกับ Pocket PC หรือ smart Phone
เอาหล่ะครับเข้าประเด็นดีกว่า ความต้องการก็คือ เรา ต้องการ ให้มีการสร้าง path ที่เครื่องปลายทางและ นำเอา file Database ของเรา รวมไปใส่ไว้ที่ path นั้น รวม Crystal report และ .Net framework ด้วย . นั่นคือทั้งหมด ที่ต้องการ
เราเริ่มด้วยการสร้าง Set up Project เราสามารถที่จะสร้างไว้ภายใน Solution เดียวกันก็ได้ หรือ สร้างไว้ต่าง Solution กันก็ได้ (แล้ว add project เข้ามาทีหลัง) ผมขอกล่าวกึงวิธีการ สร้าง ไว้ภายใน Solution เดียวกันก็แล้วกันนะครับ เพราะเวลาเราทำ Installation file ก็มักจะทำ ให้กับ solution ใด solution หนึ่งเท่านั้น
- หลังจากเรา เปิด Solution ขี้นมาแล้ว คือเมื่อเรา เปิดโปรเจคใดโปรเจคหนึ่ง มันก็จะเปิด Solution ที่โปรแจคนั้นอยู่ขึ้นมาทั้งหมด เราก็ Add โปรเจคใหม่เข้าไป โดยเราเลือก ให้เป็น “Setup Project” นะครับ แล้วตั้งชื่อให้กับโปรเจกสักหน่อยก็ได้ ไม่งั้นเราก็จะได้ชื่อ Default ซึ่ง เป็น Setup1 Setup2 .. หรืออะไรก็ตาม


- ถัดมาเราจะได้ view ที่เรียกว่า File system view ซึ่งจะมีรายการของ File system ดังนี้
- Application Folder ซึ่งมันก็คือ “C:/Program Files/ชื่อเรา[manufacturer]/projectductname”
- Profile Folder มันคือ “C:/Program Files” ของเรานั้นเอง
- User’s Desktop คือ path ที่อ้างไปยัง หน้าจอ desktop ของ User แต่ละคนนั่นเอง
- สุดท้าย ก็คือ User’s Program Menu คือ path ที่อ้างไปยัง All programs ใน start นั่นเอง
ที่จริงเราสามารถเพิ่มเข้ามาได้อีก กรณีที่เรามีความต้องการใช้งานมากกว่านี้ คลิกขวาที่ File system on target machine แล้วเลือก Add special folder ผมจะไม่กล่าวถึงในตอนนี้ นะครับเพราะเท่าที่มีนั้นเพียงพอต่อความต้องการของผมแล้ว (ฝรั่งเขาเรียกว่า commonly used ไง ยังไงก็ต้องใช้มัน )
- ขั้นตอนต่อไปคือ การทำ Project output ก็คือว่าจะให้ Project ไหนเป็นตัวโปรแกรมหลักในการ Deploy ครั้งนี้ หรือ main project นั้นแหละครับ เราผมจะให้มันสร้างภายใต้ Application Folder นะครับ ก็คลิกขวาที่ Application Folder แล้วเลือก Add –> Project output นะครับ แล้วจะมีโปรเจค ให้เราเลือก เราก็เลือกว่า โปรเจคใดที่เราต้องการ ดังรูป
เรา Project ใน combobox แล้ว Primary output ที่ Listbox ด้านล่าง นะครับ แล้วก็ OK
- เราจะได้ Primary output ขึ้นทางด้านขวามมือนะครับ หากเราสร้าง class library project ที่เราอ้างอิงจาก primary เราก็จะเห็น .dll ของ class library เหล่านั้นแสดงอยู่ด้วย ดังนี้
จะเห็นว่า ภายใต้ Application Folder ของผมมี Sub folder เพิ่อมเขามาอีก นั้นคือเราเพิ่มเขามา เพื่อให้เป็น sub folder ที่อยู่ภายใต้ folder เดียวกับ .exe file นั่นเอง คือผมต้องการให้มี folder สำหรับ database (ใส่ ฐานข้อมูล MsAccess) และ reports ที่เราเรียกใช้ระหว่างโปรแกรมทำงานนั่นเอง การเพิ่มก็เพียงแค่ คลิดขวาที่ Application folder แล้วเลือก Add->Folder เท่านั่นเอง ครับ- ส่วนไฟล์ที่ต้องการ add เข้าไปในแต่ละ Folder (database และ reports )นั้นก็ คลิกวาที่ Folder นั้นแล้วเลือก Add->file แล้วเราก็ไปเลือกไฟล์ที่เราต้องการ เช่น filename.mdb เป็นต้น มันก็จะทำการรวม file นั้นเขามาในโปรเจค เช่นเดียวกันกับ reports ซึ่งผมใช้ crystal report ในระหว่างการเขียนโปรแกรมมมันก็จะรวมอยู่กับ code file ผมก็ add มันเขามาใน folder reports ครับ แต่ข้อที่ควรรระวังก็คือ คุณตั้งกลับไปจัดการกับ การอ้างอิง file เหล่านี้อีกครั้ง ให้ตรงกับ ที่ทำไว้ใน setup project และ build ใหม่อีกครั้ง ผมหมายถึงใน primary project นะครับ
- ต่อไปเราจะทำการสร้าง shortcut ที่หน้า desktop และสร้าง item ใน Programe Menu จริงแล้วไม่ใช่เรืองยากเลย ครับ อันดับแรกเราสร้าง short cut ก่อน โดยการคลิกขวาที่ primary output from ……… (active) แล้วก็เลือก “Create shortcut to primary…… ” หลังจากนั้นเราก็จะได้ shortcut มาในชื่อเดียวกันเราก็เปลี่ยนชื่อซะให้เหมาะสม แล้วก็ ดึงไปวางไว้ – หากต้องการให้เป็น shortcut ที่หน้าจอ desktop ก็ วางไว้ที่ User’s Desktop folder ด้านซ้ายมือ
- ในกรณี User’s Program Menu นั้น หากเรามีหลาย Shortcut เราอาจต้องการทำให้เป็นกลุ่มไม่ปะปนกับ menu อื่น ๆ เราก็ต้อง สร้าง folder ภายใต้ User’s Program Menu ด้วย ใช้ชื่ออะไร ก็ตาม หลังจากนั้นเราก็ สร้าง short cut เช่นเดียวกันกับ ที่กล่าวมาก่อนหน้านี้ แล้วก็ลากมาวางไว้ที่ folder ที่อยู่ภายใต้ User’s Program Menu นี้ เราก็จะได้ menu ตามที่เราต้องการ
- นั่นเป็นทั้งหมด ที่ผมทำนะครับ อ้อมีเพิ่มเติมนะครับ เรื่อง Prerequisites … ในกรณีของผมนี่ ต้องการให้ Setup ของผม ตรวจสอบและ install crystal report สำหรับ .net framwork 2.0 ด้วย และ ก็รวมถึง framework ด้วยนะครับ เราก็สามารถที่จะกำหนดได้ดังนี้นะครับ ใน solution explorer ให้คลิกขวาที่ setup project แล้วเลือก properties นะครับจะได้หน้าจอ properties ขึ้นมาเราก็เลือกไปที่ปุ่ม prerequisites นะครับแล้วเราก็เลือก จะเอาอะไรก็ ตามต้องการ

- จากรูปนะครับ เลือกเสร็จด้านล่าง ถัดมาเราก็เลือก Download prerequisties from the same location as my application นะครับ ผมไม่แน่ใจว่า option นี้หมายถึงให้เอาจาก Installation ที่เราทำใช่หรือป่าวนะครับ เพราะผมยังได้ลอง install เครื่องอื่น เลยไม่ clear ตรงนี้ แต่ผมสังเกตุเห็นว่า ภายใต้ Debug จะมี sub folder ของ library เหล่านี้ปรากฎอยู่ อาจจะเป็นแค่ bosttrap โปรแกรมก็ได้ นะครับ คือมันเริ่มต้น และ download ส่วนที่เหลือ จาก internet นะครับ
- ในกรณี prerequisites นี้หลายท่านแนะนำว่า ให้เรา แยกต่างหากไปเลย หมายถึง ถ้าเราต้องการ ให้ผู้ใช้ลง .net framework เราให้ ผู้ใช้ install .net framework ก่อน หรือเราจัดให้พร้อมกับชุด install ก็ได้ เช่นเดียวกันกับ crystal report นะครับ หลายท่านก็บอกให้แยก ประโยชน์ก็คือ มันจะ update ตัวมันเองได้ หากมีการ update เพิ่มเติม เขาว่ามาแบบนั้นนะครับ
- ครับ เพิ่มเติมข้อที่แล้วอีกนิดนะครับ สำหรับใครต้องการ crystal report (runtime) ก็มา ได้ที่นี่นะครับ https://www.sdn.sap.com/irj/boc/businessobjects-downloads แล้วก็ คลิกที่ Get Crystal Reports, Crystal Reports Server, and Xcelsius downloads
- ส่วน .NET Framework คงไม่ต้องบอกนะครับว่า ไป download ได้ที่ไหน
- มาถึงสุดท้าย ท่านก็ build setup Project แล้วก็ลอง install ดูนะครับ แล้วก็ลอง run ดูนะครับ เรื่องการตรวจสอบ เรื่อง .net framework หรือ crystal report นั้นควรจะต้องไปหาเครื่องอื่นลงนะครับ เครื่องที่ยังบริสุทธิอยู่นะครับ ถึงจะรู้ว่า ผิดถูกประการใด ส่วนเรื่อง path ต่าง ๆ นั้นเราตรวจสอบ ได้บนเครื่องเรา แต่ต้องระวังนะครับ บางที่ เราอาจไม่เห็นขอผิดพลาดเพราะว่า บางครั้งถึงแม้เราแก้ไข path อ้างอิง ไม่หมด แล้ว ปรากฏว่าโปรแกรมทำงานได้ ที่ได้ก็เพราะว่า path เดิมของไฟล์ที่เราอ้างอิงนั้นมันยังมีอยู่ มันก็เลยยังทำงานได้ พอไปลงจริงอาจจะเกิดปัญหาได้ ต้องอรบคอบหน่อยนะครับตรงน้
- ก็คงจะจบลงตรงนี้ก่อนะครับ คิดว่าคงพอเป็นแนวทางให้กับหลาย ๆ ท่านได้ นำเอาไปต่อยอดกันเอาเอง นะครับ หากมีอะไรเพิ่มเติมหรือ ท้วงติงก็ comment มา่ได้นะครับ หรือ จะเพิ่มเติมมาก็ได้เพื่อเป็นประโยชน์กับเพื่อน ๆ คนอื่น ต่อไป นะครับ หรือจะ mail มาคุยกันก็ได้นะครับ s_teerapong2000@yahoo.com
XML Schema ความเข้าใจเกี่ยวกับ namespace
จากข้อกำหนด ของ W3C Namespace in recommendation XML Namspeces คือกลุ่มขอล elements และ attributes ที่เป็นไปตามข้อกำหนด IRI – กลุ่มของ elements และ attributes เหล่านี้เราเรียกกว่า XML vocabulary ครับ สิ่งสำคัญเลยที่ทำให้ต้องมี Namespace ก็คือการหลีกเลี่ยง naming conflicts เมื่อมีการ สร้าง XML documents และมีการ เรียกใช้หลาย ๆ ที่ หรือ เรียกใช้ Vocabularies หลาย ๆ ที่ ตัวที่ต้องใช้งาน Namespace มาก ๆ ก็คือ XML schema ซึ่ง XML schema นี้เราใช้สำหรับการกำหนด vocabulary หรือ โครงสร้างของ XML ที่เราต้องการใช้งาน แฮะ แฮะ ไม่ทราบว่าจะ get หรือปล่าว ( เราอาจจะต้องเข้าใจ XML schema และ ก็ concept ต่าง ๆ ด้วย )
Namespace ก็มีลักษณะคล้ายกับ packages ใน Java หรือ namespace ใน C# ครับ
ครับก็คง พอจะอ้างอิงให้เห็น จากความคล้ายคลึงกันนะครับ นั่นก็เป็นเพียงการ กล่าวนำเข้าเรื่อง นะครับ ถ้าไม่เห็นตัวอย่างจากการใช้งานต่าง ๆ ก็คงยังไม่ ซึ่งใช่ไหมครับ ก็เราก็จะพูดถึง ในหัวข้อต่าง ดังนี้นะครับ
- หน้าที่ ของ XML
- การประกาศ ( declare ) และการใช้งาน namspaces
- ความแตกต่างระหว่าง default-namespace และ no-namespace
- การสร้าง Namespaces โดยใช่ XML schema และ
- ความแตกต่างกันระหว่าง qualified และ unqualified element/attributes
การประกาศ และ การประยุกต์ใช้
Namespaces นั้นเราประกาศใน attribute ของ XML element ครับ และก็ไม่จำเป็นว่าเราจะต้องประกาศ มันไว้ที่ root element มันสามารถที่จะประกาศไว้ใน element ใด ๆ ก็ได้ใน XML document. นั่นก็หมายความว่า มันต้องมี scope นะสิครับ scope ของ namespace ก็ จะอยู่ภายใต้ที่ element ที่ namespace นั้นถูกประกาศครับ เริ่มตั้งแต่ opening-tag ไปจนถึง ending-tag ของ element
การประกาศ Namespace ทำดังนี้นะครับ
<someElement xmlns:pfx=”http://www.Teerapong.com”/>
จากตัวอย่างจะเห็นว่า xmlns ก็คือ attribute ของ someElement นะครับ มันใช้สำหรับการ ประกาศ namespace ซึ่งชื่อของ namespace ก็คือ http://www.Teerapong.com และ เจ้า xmlns นี้นอกจากจะประกาศ namespace แล้วยังก็หนด aliase หรือ prefix สำหรับการใช้งานอ้าง อิง namespace นี้ด้วย ฝรั่งเขาใช้คำว่า binding นะครับเวลาอ้างอิง namespace นี้ทีไหนก็ตาม ก็ใช้เฉพาะั ตัว alias หรือ prefix เท่านั้นนะครับ พอเข้าใจนะครับ อย่างเช่น เมื่อเราสร้าง XSD schema เราประกาศ แบบนี้
<xsd:schema xmlns:xsd=”http://www.w3.org/2001/XMLSchema” .. />
หลังจากนี้เมื่อเราต้องอางอิง element หรือ types ต่าง ๆ เราก็ prefix ด้วย xsd ด้วยเสมอ แม่นบ่ครับ ไม่เช่นนั้นมันก็จะฟ้อง error ต่อว่าเราว่า ไม่มี element หรือ ไม่มี type ที่กำหนด ไว้ใน http://www.w3.org/2001/XMLSchema
อันที่จริงแล้ว prefix หรือ aliase นั้นเราจะกำหนดเป็นอะไรก็ได้นะครับ ผมรู้ว่ามีคำถามนี้อยู่ในใจ เราสามารถกำหนดให้เป็นอะไรก็ได้ตามใจท่านนะครับ แต่ก็ต้องไม่ลืมนะครับ ว่าต้องให้มันสื่อความหมายด้วยไม่เช่นนั้น ปัญหาผมร่วงจะมาสู่ท่านภายหลัง นะครับ software engineer เข้าเรียกว่า meaningful นะครับ
ยังมีอีก คำถาม หนึ่งมั้ง บางคนเข้าใจว่ามันจะต้องอ้างอิงไป URL หรือป่าวเห็นประกาศเป็น URL คำตอบก็คือไม่ใช่นะครับ เราจะกำหนดให้มันเป็นอะไรก็ได้ ไม่จำเป็นต้องเป็น URL ก็ได้ เนื่องจากว่า namespace นั้นใช้สำหรับการกำหนด virtual container สำหรับที่เราจะ group XML ไว้เป็นกลุ่ม ๆ ให้ง่ายต่อการอ้างอิงใช้งาน ครับ ที่เขา URL ก็เพราะว่ามันน่าจะ unique การที่จะมีการกำหนดซ้ำกันก็คงเกิดขึ้นได้น้อย ครับ
คุณอยากจะกำหนดแบบนี้ก็ได้
<someElement xmlns:pfx=”Teerapong” />
ซึ่งก็ไม่ได้ผิดกฏ หรือ ข้อกำหนดใด ๆ แต่ก็ควรให้เป็นไปตาม W3C Namespace in XML Recommendation ก็จะดี….
ครับถึงนี่แล้วก็คงจะพอเข้าใจนะครับ การใช้ namespace อันดับแรก เราจะ bind เข้ากับ aliase หรือ prefix เสร็จแล้วเวลาอ้างอิงเราก็ใช้ aliase หรือ prefix ในการอ้างอิง namespace ผมคิดว่าก็คงจะมีข้อสงสัยตามมาอีกว่าทำไมถึงไม่ใช้ namespace อ้างอิงตรง ๆ เลยหล่ะเหตุผล ก็คือ namespace ที่เรากำหนด ตาม recommendation นั้นมันจะยาวววว และที่สำคัญก็คือมันอาจจะส่งผลกระทบอัน ใหญ่หลวงกับ syntax ของ XML ก็ได้เพราะว่า มันจะมี characters ที่ไม่อนุญาตุให้ใช้ใน tags ไงครับ 🙂
<http://www.library.com:Book /> ผิด
<lib:Book xmlns:lib=”http://www.library.com”/> ถูกต้อง
ดูตัวอย่าง เต็ม อีกนิด
<?xml version="1.0"?> <Book xmlns:lib="http://www.library.com"> <lib:Title> Sherlock Holmes</lib:Title> <lib:Author>Arthur Conan Doyle</lib:Author> </Book>
อีกตัวอย่างนะครับ
<?xml version="1.0"?> <Book xmlns:lib="http://www.library.com"> <lib:Title>Sherlock Holmes - I</lib:Title> <lib:Author>Arthur Conan Doyle</lib:Author> <Purchase xmlns:lib="http://www.otherlibrary.com"> <lib:Title>Sherlock Holmes - II</lib:Title> <lib:Author>Arthur Conan Doyle</lib:Author> </Purchase> <lib:Title>Sherlock Holmes - III</lib:Title> <lib:Author>Arthur Conan Doyle</lib:Author> </Book>
ตัวอย่างนี้ จะให้เห็น ในเรื่อง scope นะครับ สังเกตุดูจะเห็นว่ามี 2 namespace ซึ่งประกาศใน Book และ purchase contentของทั้ง แม้จะกำหนด ให้มี prefix เหมือนกันก็ตาม content ของ purchase จะอ้างอิงกับ http://www.otherlibrary.com ส่วน content ของ Book จะ อ้างอิงไปยัง http://www.library.com ดังนั้น element Title และ Author ของ Cherlock Holmes I กับ Title และ Author ของ Cherlock Holmes III จะอ้างอิงที่ namespace เดียวกัน 🙂
Default-namespace และ No-namespace
บ้างครั้งการทำงานก็มักจะเกิเอาการขี้เกียจขึ้น บ้างท่านก็ไม่อยากจะพิมพ์ prefix หรือ aliase นำหน้า element หรือ attribute ทุกครั้ง ๆ ในกรณีนี้ นะครับ เราสามารถที่จะใช้ default namespace แทนนะครับ แต่ต้องจำไว้นะครับ ว่าเราจะประกาศ default namespace ได้ที่ namespace เดียวนะครับ ไม่เช่นนั้นก็จะนำซึ่งปัญหา ผมร่วงได้นะครับ
การประกาศ default namespace นี้จะทำให้ element ใดก็ตามที่อยู่ใน scope ของ default namespace นั้น จะอ้างอิงไปยัง namespace นั้นอย่างอัตโนมัติ เขาใช้ำว่า implicitly qualifies นะครับ ( ในกรณีที่เราไม่ได้กำหนด prefix ใดใด นะครับ ก็คือ ไม่ได้ explicitly qualifies ) ใช้ technical term เยอะ ก็จะงงนะครับ แต่ผ๊มมมว่า มันได้ใจความดีกว่าใช้ภาษาไทยนะครับ อย่า บ่น ผมเลยย ดูการประกาศดีกว่า
<?xml version="1.0"?> <Book xmlns="http://www.library.com"> <Title>Sherlock Holmes</Title> <Author> Arthur Conan Doyle</Author> </Book>
ครับ นี่แหละครับ default namspace ประกาศดังนี้ทำให้เราไม่ต้อง ใส่ prefix ให้กับ element ภายใต้ scope ของ element ที่ declare default namespace นะครับ เช่นเดียวกันนะครับ ถ้าผมยกตัวอย่างเดิมดังนี้
<?xml version="1.0"?>
<Book xmlns="http://www.library.com">
<Title>Sherlock Holmes - I</Title>
<Author>Arthur Conan Doyle</Author>
<Purchase xmlns="http://www.otherlibrary.com">
<Title>Sherlock Holmes - II</Title>
<Author>Arthur Conan Doyle</Author>
</Purchase>
<Title>Sherlock Holmes - III</Title>
<Author>Arthur Conan Doyle</Author>
</Book>
ครับก็ ความหมายเหมือนกับ ตัวอย่างที่แล้วนะครับ scope ใครก็ scope มันนะครับ ถึงแม้จะไม่มี prefix ก็ตาม 🙂
Default Namespace และ Attribute
มันก็แปลก ๆ เล็กน้อยสำหรับ attribute กับเรื่อง Default Namespace ไม่สามารถใช้กันได้ เป็นงั้นซะอีก นะครับ ดังนั้นการที่จะให้ attribute มันสามารถอ้างอิงไปยัง namespace ได้ก็ต้อง explicitly qualified นะครับ สมมุติว่า element Book มี attribute ชื่อว่า isbn เราจะต้องประกาศแบบนี้นะครับ
<?xml version="1.0"?> <Book pfx:isbn="1234" xmlns="http://www.library.com" xmlns:pfx="http://www.library.com"> <Title>Cherlock Holmes</Title> <Author>Arthur Conan Doyle</Author> </Book>
วันนี้ต้อง หยุดไว้แค่นี้ก่อนะครับ แล้วจะมาต่อ ให้จบนะครับ