Group Blog
 
All Blogs
 

Web Service มีไว้เพื่ออะไร และการ Reference WCF Web Service ลงใน .NET 2.0

ช่วงที่ผ่านมาผมจำเป็นต้องทำให้ Web Service แบบ WCF สามารถถูก Reference ได้ด้วย .NET 2.0


ก่อนที่จะมาเข้าเรื่องว่าทำยังไง (ซึ่งหลังจากค้นคว้าและลองผิดลองถูกแล้ว มันง่ายนิดเดียว) เรามาดูกันก่อนดีกว่าว่าปัญหาอะไรที่ทำให้มัน Reference ไม่ได้ถ้าไม่ Config ให้ถูก

Web Service คืออะไร

ถ้าว่ากันตรงๆ ง่ายๆ มันก็คือการเอา Function ในภาษาต่างๆ ไปวางไว้บนเว็บ โดยแปลง Parameter และ Result เป็น XML ทั้งหมด เช่น Web Service กระจอกๆ เช่น Web Service ที่ยัด Function สำหรับคิดเลข รับพารามิเตอร์ 3 ตัวคือ เลขแรก Operator และเลขที่สอง

แบบเก่า: http://simpleservice/calculator?No1=2&Operator=Plus&No2=4

แบบ Service : http://simpleservice/calculator?XML=<1stNumber>2+<2ndNumber>4

แล้ว Response กลับมาได้ 6

อันนี้พูดแบบกระจอกๆ แต่จริงๆ XML ที่ส่งไปส่งมามันมีมาตรฐานมากกว่านั้น ลองหาเสริมความรู้ได้

Web Service มันเหนือกว่า Post/Response แบบเว็บโบราณ ตรงที่เราสามารถเรียกดูได้ว่าแต่ละ Service มีหน้าที่อะไร แล้วต้องการพารามิเตอร์อะไรบ้าง ซึ่งการเรียกหาพารามิเตอร์ ภาษาสากลจะเรียกว่าการถาม WSDL ทำให้ไม่เหมือนแบบเก่าตรงที่แบบเก่าเราต้องรู้มาก่อนว่า Web function แต่ละตัวต้องการอะไรบ้าง แล้วถ้าสิ่งที่ต้องการมันเปลี่ยน ก็ต้องให้โปรแกรมเมอร์ที่เขียน Function คอยอัพเดทให้คนใช้ทุกคน ถ้าเป็น Web Function แบบที่ใช้ในองค์กรคงไม่เป็นไร แต่นึกถึงสมมติคุณทำงาน Reuter แล้วเขียน Web Function ที่มีองค์กรทั่วโลก 1000 กว่าแห่งใช้สิครับ คุณจะมานั่งเมล์ไปทุกที่ว่าตอนนี้อัพเกรดใหม่ พารามิเตอร์เปลี่ยน คงใช้เวลาเป็นเดือนกว่าจะปรับความเข้าใจได้หมด แต่ถ้าเป็น Web Service คุณแค่อัพเดท WSDL คนทั่วโลกที่ใช้ก็จะรู้ทันทีว่าคุณอัพเดทใหม่แล้วนะ

ในทางทฤษฎีแล้ว Web Service ควรจะสามารถ Reference กันได้ เพราะเป็นบริการกลาง ไม่ว่าคุณจะใช้ Java C# C++ Ruby หรือภาษาอะไร ก็สามารถเรียกใช้ Service ได้ทั้งสิ้น เนื่องจากบริการกลางนี้คุยกันด้วย XML ดังนั้นขอแค่ภาษาคุณอ่าน XML ได้ ก็สามารถติดต่อกับ Web Service ได้ หรือแม้แต่ภาษาคุณอ่าน XML ไม่ได้ แต่คุณสร้างตัวอ่าน หรือพยายามแกะ Response ออกมาเอง ก็สามารถคุยได้เช่นกัน

แต่ในทางปฏิบัติ เนื่องจากเราคงไม่มานั่งแกะ XML ด้วยตัวเอง ซึ่งแต่ละภาษาก็มีตัวแกะให้ที่จะแปลงจาก XML มาเป็น Class Object ดังนั้นเราจึงต้องทำให้ XML ที่คืนออกมานั้นใช้งานได้บนภาษา

ปัญหาเรื่อง XML

สำหรับ XML นั้นมีเรื่องนึงที่ผมไม่ชอบเอามากๆ คือหัวของ XML จะมีการใส่ namespace เพื่อทำให้คลาสที่ส่งและคลาสที่รับอยู่ใน namespace เดียวกัน เช่น สมมติคลาสชื่อ SomeClass อยู่ใน MyNameSpace.SomeClass ถ้าหัวของ XML เป็น

 

แบบนี้ถ้าคุณพยายาม เอา SomeClass ที่มี Property, Field, Method เหมือนกันทุกประการ แต่อยู่ใน NameSpace2.SomeClass มารับข้อมูลจาก XML ตัวนี้ มันจะรับไม่ได้ เพราะมันคิดว่าเราเข้าใจผิดพยายามเอาอีกคลาสนึงมารับ

ตามทฤษฎีมันก็ควรจะเป็นเรื่องดี แต่ในทางปฏิบัติแล้ว หลายๆ ครั้งคลาสส่งกับคลาสรับ ตัวโปรแกรมเมอร์รู้อยู่แล้วว่ามันเป็นคลาสเดียวกัน แต่คนละ namespace อยากจะให้ใช้กันได้ แต่ .NET จะไม่ยอมให้ใช้แทนกัน เพราะมันตีความว่าเราโง่ เราเข้าใจผิด พยายามเอาข้อมูลในคลาสคนละคลาสมายัดใส่กัน ทั้งๆ ที่หลายครั้งเราไม่ได้เข้าใจผิด (สิ่งนี้ทำให้หลายๆ คนเลือกใช้ JSON แทนแล้วในการส่งข้อมูลผ่านเว็บ เพราะ JSON ไม่มี namespace บังคับ ขอแค่เรามีคลาสที่พอยัดได้ มันยัดให้เลย)

ทีนี้พอโปรแกรมเมอร์บ่นกันเยอะๆ Microsoft แก้ปัญหานี้ใน .NET 4.0 โดยการบังคับว่าคลาสที่โปรแกรมเมอร์รู้ว่าคนละ namespace แต่เป็นคลาสเดียวกัน จะใส่ namespace เป็น DataContract หมดเลย


แล้วคลาสไหนที่จะใช้รับส่งข้อมูล ก็ใส่ [DataContractAttribute] ไว้ข้างบนหัวคลาส ตัว XML Generator จะทำการแปลง namespace ให้เอง

Header ของ XML นอกจาก namespace แล้วยังมีอีกหลายค่า เช่น บอกว่า XML เวอร์ชั่นอะไร ส่งอะไรมา ซึ่ง Header ตัวนี้ Microsoft จะค่อนข้างบีบบังคับว่า ถ้า Header ผิด จะไม่ยอมทำอะไรเลย อย่างแค่ NameSpace ผิด ทั้งๆ ที่ข้อมูลมันยัดกันได้ ก็ไม่ยอมยัดให้

ปัญหาการ Reference ด้วย .NET 2.0

ปัญหาคือใน WCF Web Serivce โดยทั่วไป จะแปะ Header มาว่า type ของ XML เป็น "Application/XML" แต่ตัว .NET 2.0 จะรับเฉพาะ type "Text/xml" ซึ่งวิธีทำให้ WCF Web Service นั้นทำ type Text/XML ต้องเซ็ต 2 อย่าง (สำหรับรายละเอียด ลองอ่านในเว็บ Microsoft นะครับ)

1. Binding Config ต้องใช้ WebHttpBinding
2. Message Encoding ต้องใช้ Text

แค่นี้ก็ได้แล้วครับ

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




 

Create Date : 04 กันยายน 2555    
Last Update : 4 กันยายน 2555 9:10:27 น.
Counter : 2397 Pageviews.  

ทักทาย

หลังจากทำงานมาได้ซักพักแล้วเจอปัญหาหยิบย่อยมากมายระหว่างการพัฒนาซอฟต์แวร์ ซึ่งหลายๆ ปัญหาเป็นอะไรที่ปัญญาอ่อนเอามากๆ แต่ด้วยความไม่รู้จึงต้องเสียเวลาค้นหาสาเหตุนานเกินไป ก็คิดได้ว่าน่าจะทำบันทึกไว้เผื่อต้องกลับมาแก้ปัญหาเดิมอีก แต่ไหนๆ ก็ไหนๆ ก็ทำบันทึกใน facebook เผื่อแชร์ความรู้ให้เพื่อนๆ ที่อยู่ในวงการเดียวกันเลยน่าจะดีกว่า จะพยายามอัพเดทเรื่อยๆ ถ้าไม่ขี้เกียจนะครับ




 

Create Date : 30 สิงหาคม 2555    
Last Update : 30 สิงหาคม 2555 21:31:19 น.
Counter : 211 Pageviews.  

การโหลด Form มันเป็น Async!!!!

วันนี้อยากอัพเดทมาก ถึงแม้งานจะยุ่งแค่ไหน เพราะปัญหาที่เกิดขึ้นวันนี้มันน่าสนใจจริงๆ

โจทย์ที่พี่ Senior ทำในวันนี้คือต้องการให้มี Windows Form เล็กๆ โชว์ขึ้นมาก่อนโปรแกรมเริ่ม บอกว่าขณะนี้โปรแกรมหลักใกล้โหลดเสร็จหรือยัง ซึ่งวิธีการคือ

1. ตั้ง Event ใน Constructor ของ Form หลัก ว่าระหว่าง Load Constructor ให้ Update UI ของ FormProgress ที่แสดงอยู่ เพื่อความปลอดภัยเราก็ต้องเข้าไปทำบน UI Thread ของ FormProgress

Sample : Form1(FormProgress FormInstance)

{

.....

FormInstance.Dispatcher.Invoke(()=> FormProgress.Progress=2);

.....

FormInstance.Dispatcher.Invoke(()=>FormProgress.Progress=3);

}

แล้วในตัวทางเข้าของโปรแกรม ก็ตั้งไว้ว่า

Main()

{

Thread t=new Thread(new ThreadStart(()=>

{

FormProgress.Show()

var FormMain=new Form1(FormProgress) // โยน FormProgress เข้าไปให้ Constructor อัพเดทค่า

})); // แตก Thread ออกมาเพื่อทำการโหลด FormMain พร้อมกับแสดงฟอร์มนี้

}

เหมือนจะไม่มีอะไรผิดพลาด Thread UI ก็แตกออกมาถูกหมดแล้ว Managed ไว้หมดแล้ว ปรากฎว่าไม่มีอะไรเกิดขึ้นเลย แล้วโปรแกรมก็ค้างยาววววว

หลังจากค้นอยู่นาน ผมได้ข้อสรุปคือ สาเหตุนั้นเป็นเพราะ FormInstance.Show() เป็น ASYNCHRONUS METHOD!! เหลือเชื่อนะครับที่เมธอดที่เราใช้อยู่ทุกวันจะเป็น Async ได้โดยที่เราไม่รู้ตัว 

แล้วการที่มันเป็น Asynchronus Method แปลว่าอะไร ทำไมมันทำให้เราทำงานไม่ได้กันล่ะ ถ้าถามแบบนี้ เราจะลงลึกกันนิดนึงครับ

Asynchronus Method คือ Method ที่เราแค่เข้าไปสั่งให้มันทำ แต่ไม่ได้รอจนมันเสร็จ เช่น ถ้ามี Method ชื่อ WaitAndGetNumber() ถูกเขียนไว้ว่า

int a;

void WaitAndSetNumber()

{

Thread.Sleep(10000); //รอ 10 วินาที

a=200;

}

ถ้าเราเรียกใช้แบบ Sync

WaitAndSetNumber();

int b = 100;

การทำงานของมันจะเป็นว่า รอ 10 วินาที => ส่งค่า 200 ให้ a => กำหนด b เป็น 100

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

WaitAndSetNumberAsync();

int b=100;

Thread Main:สั่งให้เริ่มทำงาน => กำหนดให้ b เป็น 100

Thread 2: ถูกสั่งให้เริ่มทำ => รอ 10 วินาที => กำหนด a เป็น 200

บรรทัด b=100; จะทำต่อทันที เพราะบรรทัด WaitAndSetNumberAsync(); เป็นแค่การ "สั่งให้เริ่มทำ" ไม่ใช่การ "ให้ทำจนเสร็จ" การ "แค่สั่งให้เริ่มทำ แล้วลุยบรรทัดถัดไปทันทีโดยไม่รอให้เสร็จ" แบบนี้แหละครับที่เค้าเรียกกันว่า Asynchronus Method

ย้อนกลับมาที่ปัญหา ปัญหานี้หลังจากลองแล้วสามารถแก้ไขได้โดยเปลี่ยนจาก FormProgress.Show() เป็น FormProgress.ShowDialog() ซึ่งทำให้ผมสรุปว่า โครงสร้างภายในของ .Show() นั้นเป็น Asynchronus ซึ่งก็สอดคล้องกับที่ว่ามันเป็น non-modal form

อธิบายคือ .Show() นั้นจะไม่รอให้การวาดฟอร์มเสร็จ จะไปทำบรรทัดถัดไป new FormMain ทันที แล้วพอ FormMain พยายามจะเรียก Dispatcher ของ FormProgress ที่ยัง Show ไม่เสร็จ ทุกอย่างก็จะค้าง แต่พอเปลี่ยนมาใช้ ShowDialog() ซึ่งจะรอให้วาดฟอร์มเสร็จแล้วจึงทำงานบรรทัดถัดไป จึงสามารถมาเรียก Dispatcher ของ FormProgress ได้

ทั้งหมดนี่ไม่มีข้อมูลอ้างอิง ผมสันนิษฐานจากเหตุการณ์ที่เกิดขึ้นล้วนๆ อ่านใน MSDN ก็ไม่มีใครบอกว่า .Show() โครงสร้างภายในระหว่าง "วาด" มันทำบน Thread ใหม่ หรือทำบน Thread ที่เรียกใช้งานมันกันแน่ เพราะอันนี้เราเล่นแตก Thread แล้วใช้งานกันระหว่างวาดไม่เสร็จเลยทีเดียว





 

Create Date : 30 สิงหาคม 2555    
Last Update : 30 สิงหาคม 2555 21:29:52 น.
Counter : 430 Pageviews.  

Registry Key 32/64 Bit ต่างกันไฉน

วันนี้เจอ bug ของโปรแกรมที่เขียนไปแล้วอย่างแปลกประหลาด คือเนื่องจากเราขาย Solution ดังนั้น Software ที่ทำก็จะไม่ได้มีแค่ Module เดียว ทีนี้มันมีปัญหาตรงที่ว่าโปรแกรมที่ไว้ Config ค่าต่างๆ ของระบบเราทั้งหมด ดันไม่สามารถส่งค่าที่ Config เสร็จไปให้โปรแกรมตัวอื่นๆ ในระบบได้ ไม่ว่าจะพยายามตั้งค่าแค่ไหน ซึ่งน่าแปลกตรงที่เราเขียนไปนานมากแล้วทำไมถึงพึ่งมาเป็น

หลังจากมานั่งไล่แกะดูอยู่นาน ในที่สุดก็พบสาเหตุแล้ว มันเกิดจากระบบป้องกัน Registry ของ Windows นั่นเอง

ถ้าเราทำงานบนเครื่องที่เป็น 64-bit เวลาที่โปรแกรมที่เป็น 32 bit พยายามจะเขียน Registry มันจะล็อกส่วนไว้ไม่ให้ไปยุ่งกับ Registry ของโปรแกรม 64 Bit คือโปรแกรม 32-bit จะเขียน Registry ได้เฉพาะภายใต้ HKEY_LOCAL_MACHINESOFTWAREWow6432Node เท่านั้นที่ผ่านมาระบบของเราคือจะเขียน Config ลง Registry แต่เผอิญโมดูลที่ใช้เขียน Config ดันถูก Compile เป็น 32 bit ส่วน Module ที่ใช้อ่าน Config ถูกคอมไพล์เป็น Any Cpu ดังนั้น Registry ที่โมดูลเขียน Config พยายามแก้ จึงไม่ใช่จุดที่โมดูลทำงานจริงอ่าน

บทเรียนวันนี้คือ เวลาทำ Software Solution ขนาดใหญ่ที่ต้องมีหลายโปรแกรมประกอบเข้าด้วยกัน (ถ้ายกแบบที่่รู้จักกันทั่วก็เช่น Microsoft Office) การจะมี Config รวมต้องระวังเรื่อง 32,64 bit ให้ดี ควรจะเทสบนเครื่อง 64,32 พร้อมๆ กันก่อนส่งให้ลูกค้านะคร้าบ 




 

Create Date : 30 สิงหาคม 2555    
Last Update : 30 สิงหาคม 2555 21:28:49 น.
Counter : 403 Pageviews.  

Bad OOP Design

วันนี้เป็นวันที่เริ่ม Implement Windows Service แบบจริงๆ จังๆ ดู พอดูโค้ดเก่าที่เคยทำไว้ ผมพบว่าบางสิ่งที่ผมคิดว่าเป็น Bad Design ใน OOP 

- การนำ ErrorMessage เข้าไปรวมในคลาสรับส่งข้อมูล

จริงอยู่ว่าการรับส่งข้อมูลระหว่างฐานข้อมูล เน็ตเวิร์ก หรือ Thread ต่างๆ บนโปรแกรมเดียวกัน ควรจะมีตัวบ่งบอกว่าการรับส่งข้อมูลสำเหร็จมั้ย และถ้าไม่สำเร็จเกิดจากอะไร แต่ที่ผมพบคือ การรวม ErrorMessage และ Error ที่เกิดเข้าไปในคลาสรับส่งข้อมูลเลยทันที เช่น

class SomeMessage

{

object Content1;

object Content2;

bool IsSuccess;

string ErrorMessage;

}

การ Design Class แบบนี้นั้นถึงใครจะมองว่ายังไง แต่ผมคิดว่ามันแย่มากๆ เหตุผลเพราะเมื่อมี Instance ของคลาสดังกล่าวออกมา เราจำเป็นต้องเช็คตลอดเวลาว่ามันมี ErrorMessage ติดมาด้วยหรือเปล่าก่อนใช้ ซึ่งเรามีเครื่องมือที่มีประสิทธิภาพอย่าง OOP แล้วทำไมเราต้องมานั่งเช็คแบบนี้อีก ลองนึกภาพของหลักการ OOP ที่มองทุกๆ Instance เป็นวัตถุ ถ้าเราต้องมานั่งเช็คด้วยมือตลอดเวลาว่าวัตถุที่เราใช้งานมีตำหนิหรือเปล่า (Error หรือไม่) มันก็ดูผิดประหลาด

ถ้าเอาให้เห็นภาพผมลองยกตัวอย่างว่าเรามีคำสั่ง

ProcessMessage(SomeMessage Parameter)

ถ้าเรา Design แบบข้างบนแปลว่าทุกครั้งที่เราเรียก ProcessMessage ต้องใช้อย่างน้อย 2 บรรทัดเสมอ

If (Message.IsSuccess) ProcessMessage(Message);

  ซึ่งถ้าเรามี Method ที่รับ SomeMessage เยอะๆ แล้วเราจะทำยังไงล่ะ มานั่งเช็คทุกรอบเหรอ??? จริงๆ เราอาจจะเช็คแต่แรกก็ได้ เช่น var message=GetMessage(); if (!message.IsSuccess) message=null; แต่เราจะมั่นใจได้อย่างไรว่าเราจะไม่เผลอพลาดลืมเช็คไปเมื่อเขียนโค้ดเยอะๆ แถม null reference นี่เป็นปัญหา debug ยากอันดับต้นๆ

วิธีการออกแบบที่ดีเราควรจะแยกส่วน Content และ ErrorMessage ออกจากกัน แล้วคลาสที่รับส่งข้อมูลผ่านเน็ตเวิร์ก ฐานข้อมูล หรือ Threading ควรจะไม่สามารถทำอย่างอื่นได้เลยจนกว่าจะเช็คแล้วว่าการรับส่งสมบูรณ์ ถ้าการ Design แบบสมบูรณ์จริงๆ ผมคิดว่าน่าจะเป็นแบบนี้

class SomeMessage

{

object Content1; 

object Content2;

}

class ConnectionMessage

{

private object Content;

int ErrorCode;

string ErrorMessage;

public object GetContent()

{

if (ErrorCode!=0) throw new Exception("Error Getting Data");

return Content;

}

}

แล้วจึงเอา SomeMessage ไปยัดไว้ใน ConnectionMessage อีกที การทำแบบนี้จะทำให้รับประกันว่าไม่ว่าอนาคตเราจะเขียนตกหล่นยังไง แต่ถ้าเราพยายามจะดึงข้อมูลที่ไม่สมบูรณ์มี Error ออกมาเมื่อไหร่ จะได้ Exception ตามมาทันที แล้วเราจะเอา ConnectionMessage ไปใส่ใน Method ต่างๆ โดยไม่ดึง Content ออกมาก่อนก็ไม่ได้ จึงรับประกันได้เลยว่าถ้าเราซุ่มซ่ามพยายามดึงข้อมูลที่ส่งผ่านกันไม่สำเร็จเมื่อไหร่ มันจะเกิด Exception ทันที ซึ่งจะดีกว่าเกิด Bug เอามากๆ

ปล. แต่รับสารภาพว่าตัวเองก็ไม่ได้เขียนถึงขั้นล็อกไว้ให้ GetContent ยิง Exception ออกมา เพราะขี้เกียจครับ ก็แค่ห่อไว้ใน ConnectionMessage เท่านั้น นิสัยเสียจริงๆ




 

Create Date : 30 สิงหาคม 2555    
Last Update : 30 สิงหาคม 2555 21:28:27 น.
Counter : 265 Pageviews.  

1  2  

Mariel
Location :


[Profile ทั้งหมด]

ให้ทิปเจ้าของ Blog [?]
ฝากข้อความหลังไมค์
Rss Feed
Smember
ผู้ติดตามบล็อก : 1 คน [?]




Friends' blogs
[Add Mariel's blog to your web]
Links
 

 Pantip.com | PantipMarket.com | Pantown.com | © 2004 BlogGang.com allrights reserved.