Category: Developer

งานพัฒนาระบบ, เขียนโปรแกรม

  • Itextsharp #4 คู่มือเทคนิคพื้นฐานการใช้งาน PdfTable สำหรับมือใหม่ ตอนที่ 2

    บทความนี้สำหรับผู้ที่ยังไม่ได้อ่านบทความก่อนหน้านี้ สามารถอ่านบทความได้ที่ Itextsharp #3 คู่มือเทคนิคพื้นฐานการใช้งาน PdfTable สำหรับมือใหม่ ตอนที่ 1 เพื่อความเข้าใจต่อเนื่องกันนะครับ โดยเนื้อหาในบทความนี้เป็นส่วนของใช้งาน Table ที่ผู้เขียนเองใช้ในการทำงานคือการทำ Nest Table นั้นเองโดยปกติการสร้างเอกสาร1ใบ ผู้เขียนจะใช้ Table ตัวแรกในการกำหนดรูปแบบหน้าตาของเอกสาร การจัดตำแหน่งสัดส่วนต่างๆ หลังจากนั้นก็ใช้ Table ซ้อนเข้าไปตามส่วนต่างๆตามที่ออกแบบไว้มาจัดการส่วนของข้อมูล จึงจำเป็นต้องใช้งาน Nest Table เราไปดูตัวอย่างรูปกันก่อนดีกว่าครับ

    จากตัวอย่างจะพบว่าสำหรับคนที่เขียนโปรแกรมก็คือการซ้อนตารางปกติที่พบเจอได้ในการเขียนโปแกรม จากรูปคือเอาTable2 ใส่ใน Cell ที่1ของ Table1 หรือพูดภาษาของเขียนโปรแกรมคือเขียน Table2 ลงใน <TD> แรกของ Table1 นั้นเอง ซึ่งถามว่าทำงานคล้ายๆกับตารางทั่วไปแล้วสามารถรวมCell ก่อนเพิ่มตารางได้ไหมใน ItextSharp ทำได้เหมือนกันครับ ตามรูปเลยครับ

    จากตัวอย่างเป็นการรวม Collumn แล้วทำการเพิ่ม Table2 เข้าไปโดยใน ItextSharp เรากำหนดCollumn ของTable1และTable2 เท่ากัน ตัวโปรแกรมจะจัดขนาดของ Cell ให้เท่ากันอัตโนมัติเมื่อทำการแสดงผลเหมือนในรูปตัวอย่างครับ ส่วนการเขียนโปแกรมนั้นง่ายมากๆเพิ่มขั้นตอนจากตอนที่1มานิดเดียว เราไปดู Code กันเลยดีกว่าครับ

    PdfPCell PdfCell = null;
    
    PdfPTable PdfTableH = new PdfPTable(3);
    float[] tbHwidths = { 50f, 50f, 50f };
    PdfTableH.SetWidths(tbHwidths);
    PdfTableH.WidthPercentage = 100;
    
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 2 Cell 1", bold)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTableH.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 2 Cell 2", bold)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTableH.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 2 Cell 3", bold)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTableH.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 2 Cell 4", bold)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTableH.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 2 Cell 5", bold)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTableH.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 2 Cell 6", bold)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTableH.AddCell(PdfCell);
    
    //-----------------------------------------------------------------//
    
    PdfPTable PdfTable = new PdfPTable(3);
    float[] tbwidths = { 50f, 50f, 50f };
    PdfTable.SetWidths(tbwidths);
    PdfTable.WidthPercentage = 100;
    
    PdfCell = new PdfPCell(PdfTableH);// <---------------------------------#1
    PdfTable.AddCell(PdfCell);
    
    //PdfTable.AddCell(new PdfPCell(PdfTableH)); <-------------------------#2
    
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 1 Cell 2", fnt)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTable.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 1 Cell 3", fnt)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTable.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 1 Cell 4", fnt)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTable.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 1 Cell 5", fnt)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTable.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 1 Cell 6", fnt)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTable.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Table 1 Cell 7", fnt)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfTable.AddCell(PdfCell);
    

    ตัวอย่าง Code ผู้เขียนแบ่งเป็น 2ส่วนด้วยกันคือส่วนแรกเป็นการสร้าง Table2ก่อน เพราะเราจะเอาTableนี้ไปใส่ในTable1อีกที การเขียนก็เหมือนจากตัวอย่างที่ผ่านๆมาในบทความก่อนหน้านี้ เรามาดูส่วนที่2กันส่วนที่ต่างจากส่วนแรกคือ บรรทัดที่(#1) PdfCell = new PdfPCell(PdfTableH); โค๊ดนี้คือการเพิ่ม Table2 ลงไปใน Cell ที่เราต้องการครับ หรือจะใช้อีกรูปแบบ(#2)คือ PdfTable.AddCell(new PdfPCell(PdfTableH)); แค่นี้เราก็ทำ NestTable เสร็จสิ้นเป็นที่เรียบร้อย ง่ายไหมครับ แล้วถ้าเราจะรวม Cell ก่อนเพิ่ม Table ละจะทำยังไง หลายคนอาจทำได้โดยไม่ต้องดูตัวอย่างของผู้เขียนแล้วก็ได้ครับ แต่เราไปดูตัวอย่างกันดีกว่าครับ

    PdfCell = new PdfPCell(PdfTableH);
    PdfCell.Colspan = 3;
    PdfTable.AddCell(PdfCell);
    หรือ
    PdfTable.AddCell(new PdfPCell(PdfTableH) { Colspan = 3 });
    

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

    จากรูปตัวอย่างเอกสารประกอบไปด้วยส่วนของ Header Table1 ที่แสดงผลทุกหน้ากระดาษ และมีส่วนของเนื้อหาในหน้าแรกเป็นส่วนของ Body Table1 แล้วมีตารางของข้อมูลจำนวนหลายรายการโดยเมื่อตารางนี้ขึ้นหน้าใหม่ให้ทำการแสดงหัวตารางไปด้วย ผู้เขียนจะให้ส่วนนี้เป็น Table2 หรือ NestTable นั้นเอง ถ้าทุกท่านนึกภาพออกแล้วเราไปดูตัวอย่าง Code กันดีกว่าครับ

    PdfPCell PdfCell = null;
    
    PdfPTable PdfTableH = new PdfPTable(3);
    float[] tbHwidths = { 50f, 50f, 50f };
    PdfTableH.SetWidths(tbHwidths);
    PdfTableH.WidthPercentage = 100;
    PdfTableH.HeaderRows = 1;
    
    PdfCell = new PdfPCell(new Phrase(new Chunk("Header Table 2", bold)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfCell.Colspan = 3;
    PdfCell.FixedHeight = 20f;
    PdfTableH.AddCell(PdfCell);
    
    for (int i = 0; i < 60; i++)
    {
        PdfCell = new PdfPCell(new Phrase(new Chunk("Body Table 2", fnt)));
        PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
        PdfCell.PaddingBottom = 8;
        PdfTableH.AddCell(PdfCell);
    }
    
    PdfPTable PdfTable = new PdfPTable(1);
    float[] tbwidths = { 500f };
    PdfTable.SetWidths(tbwidths);
    PdfTable.WidthPercentage = 100;
    PdfTable.HeaderRows = 1;
    PdfTable.SplitLate = false;
    PdfTable.KeepTogether = true;
    
    PdfCell = new PdfPCell(new Phrase(new Chunk("Header Table 1", fnt)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfCell.PaddingLeft = 10;
    PdfTable.AddCell(PdfCell);
    PdfCell = new PdfPCell(new Phrase(new Chunk("Body Table 1", fnt)));
    PdfCell.HorizontalAlignment = Element.ALIGN_CENTER;
    PdfCell.PaddingBottom = 8;
    PdfCell.PaddingLeft = 10;
    PdfTable.AddCell(PdfCell);
    PdfTable.AddCell(new PdfPCell(PdfTableH));
    

    จาก Code ตัวอย่างผู้เขียนได้นำคำสั่ง HeaderRows มาใช้งานร่วมด้วยเพื่อให้ทำการยึดหัวตารางทั้งสองตารางเอาไว้ในหน้ากระดาษในหน้าถัดๆไปด้วย และผู้เขียนได้เพิ่มคำสั่งมาอีก 2 คำสั่งด้วยกัน เป็นคำสั่งเสริมขึ้นมาเพื่อให้การแสดงผลของข้อมูลสวยงามและถูกต้องตามที่เราต้องการ คือ PdfTable.SplitLate = false; และ PdfTable.KeepTogether = true; คำสั่งดังกล่าวใช้ในกรณีเราทำ NestTable ที่มีการแสดงผลมากว่า1หน้ากระดาษ แต่ถ้าเอกสารที่ทำมีเพียง1หน้ากระดาษแน่นอน คำสั่งดังกล่าวไม่ต้องเขียนก็ได้ครับ แล้วถ้าไม่ใส่ไปเลยได้ไหมถึงเอกสารจะมีหลายหน้า ผู้เขียนแนะนำว่าควรใส่ เพราะไม่มีคำสั่งนี้ Nest Table จะไม่แสดงผลในหน้าแรกเสมอครับแต่จะเลื่อนไปแสดงผลในหน้าที่สองแทน เนื่องจาก Itextsharp ทำการเลื่อนให้เองอัตโนมัติเมื่อตารางนั้นล้นเกินหน้ากระดาษแรก นั้นเอง

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

  • การเรียกใช้งาน (Register) Bootbox.js ด้วย C#.NET

    ในการออกแบบเว็บฟอร์มบันทึกข้อมูลลงฐานข้อมูลนั้น นักพัฒนาเว็บแอ๊พปลิเคชั่นส่วนใหญ่จะออกแบบเป็นลำดับขั้นตอนการทำงานประมาณนี้

    1. ผู้ใช้เปิดเว็บฟอร์มขึ้นมาด้วยเว็บบราวเซอร์
    2. ผู้ใช้กรอกข้อมูลลงในเว็บฟอร์ม
    3. ผู้ใช้กดปุ่มบันทึกข้อมูล
    4. ระบบตรวจสอบ (Validate) ข้อมูลที่ผู้ใช้กรอกเข้ามา
      1. ถ้าผ่านก็บันทึกข้อมูลลงฐานข้อมูล
      2. ถ้าไม่ผ่านก็จะแจ้งข้อความแจ้งเตือนผู้ใช้งานให้ตรวจสอบข้อมูลใหม่อีกครั้ง
    5. ระบบจะแจ้งผลการบันทึกข้อมูลว่า
      1. “บันทึกข้อมูลสำเร็จ” กรณีที่บันทึกสำเร็จ หรือ
      2. “เกิดข้อผิดพลาดในการบันทึกข้อมูล” ในกรณีที่มีข้อผิดพลาด

    การพัฒนาเว็บด้วย ASP.NET นั้นในขั้นตอนที่ 4 จะถูกเขียนด้วยโค้ด C#.NET ซึ่งเป็น Server Side Script ส่วนขั้นตอนที่ 5 นั้นเราสามารถใช้ Dialog ของ Bootbox.js ซึ่งเป็น Client Side Script มาช่วยได้ ซึ่งในบทความนี้จะแสดงวิธีการเขียนโค้ด C#.NET ให้เรียกใช้ Bootbox Dialog ได้เลย  (อ่านบทความ การสร้าง JavaScript Dialog ด้วย Bootbox.js ได้ที่นี่) และสร้างเป็นฟังก์ชั่นสำเร็จรูปไว้เรียกใช้งานใหม่ได้ (Reuse)

     

    การรันคำสั่ง Bootbox.js ซึ่งเป็น JavaScript ผ่าน C#.NET นั้นจะต้องใช้ ScriptManager ช่วยลงทะเบียนสคริปต์ (Register Script) ดังนี้

    รูปที่ 1 การ Register Script ด้วย ScriptManager

    เพียงเท่านี้ก็สามารถนำส่วนของโค้ดดังรูปที่ 1 ไปใช้ในการแสดงข้อความแจ้งผู้ใช้หลังการบันทึกข้อมูลลงฐานข้อมูลได้แล้ว โดยมีตัวอย่างดังรูปที่ 2

    รูปที่ 2 การแสดงข้อความแจ้งผู้ใช้หลังการบันทึกข้อมูล

    ผลลัพธ์ที่ได้จากโค้ดข้างต้นจะเป็นดังรูปที่ 3

    รูปที่ 3 ผลลัพธ์ของข้อความแจ้งเตือนจากการบันทึกข้อมูล

    แน่นอนว่าในการพัฒนาเว็บแอ๊พปลิเคชัน 1 ครั้ง จะประกอบด้วยฟอร์มบันทึกข้อมูลมากมาย ทำให้ต้องเขียนโค้ดซ้ำๆ กันหลายครั้ง จากโค้ดข้างต้นเราสามารถ Optimize โค้ดได้อีกโดยให้สามารถเรียกใช้งานและแก้ไขโค้ดในภายหลังได้ง่ายขึ้น โดยการย้ายส่วนลงทะเบียนสคริปต์ไปไว้ในฟังก์ชั่นกลางสร้างเป็น Library (ในที่นี้จะตั้งชื่อว่า CoreBLL) สำเร็จรูปไว้ใช้ต่อไปดังนี้

    รูปที่ 4 CoreBLL

    หลังจากที่สร้าง CoreBLL เสร็จแล้วให้แก้ไขโค้ดบันทึกข้อมูลดังรูปที่ 5

    รูปที่ 5 โค้ดบันทึกข้อมูลลงฐานข้อมูล

    จะเห็นว่า CoreBLL ทำให้โค้ดในการบันทึกข้อมูลฟอร์มสั้นลง ง่ายขึ้น และช่วยลดความผิดพลาดในการเขียนโค้ดลง ในขณะที่หน้าจอผลลัพธ์ (รูปที่ 3) ยังคงเหมือนเดิม

     

  • การสร้าง JavaScript Dialog ด้วย Bootbox.js

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

    JavaScript Confirm บน Google Chrome

     

    JavaScript Confirm บน Mozilla Firefox
    ตัวอย่างโค้ดเรียกใช้งาน JavaScript Confirm

    จากรูปจะเห็นว่า JavaScript Confirm บน Google Chrome และ Mozilla Firefox หน้าตาไม่เหมือนกัน ทั้งๆที่ใช้โค้ดเดียวกัน และปัญหาอีกอย่างหนึ่งที่พบคือ หากเราต้องการให้ข้อความ “คุณต้องการลบข้อมูลรายการนี้ใช่หรือไม่?” มีการขึ้นบรรทัดใหม่ ใส่สี เพิ่มขนาดฟอนต์ จัดตัวหนา ตัวเอียงก็ไม่สามารถทำได้เลย แต่ถ้าต้องการให้แก้ปัญหาเหล่านี้ได้ก็ต้องใช้ตัวช่วย นั่นคือ Bootbox.js ซึ่งเป็น JavaScript library ที่ใช้งานร่วมกับ Bootstrap โดยใช้ Bootstrap modal มาทำหน้าที่แทน JavaScript Dialog ต่างๆ ทั้ง Alert, Confirm, Prompt และรวมถึง Custom Dialog ด้วย ทำให้หน้าตาของ Dialog จะเหมือนกันทุก Browser และสามารถจัดรูปแบบการแสดงผลได้ตามต้องการโดยใช้ CSS

    วิธีการติดตั้ง

    1. เข้าไปที่เว็บไซต์ http://bootboxjs.com และเลือกดาวน์โหลดไฟล์ชื่อ bootbox.min.js หรือถ้าไม่ต้องการดาวน์โหลดก็สามารถใช้ CDN (https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js) ได้เช่นกัน
    2. ใส่โค้ดอ้างอิงไปยัง bootbox.min.js ใน header tag ดังนี้
      <script src=”path/to/script/bootbox.min.js”></script>
      หรือ
      <script src=https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js></script>

    ตัวอย่างการใช้งาน Confirm Dialog

    1. กำหนดชื่อ CSS class ให้กับปุ่มลบ ในที่ตั้งชื่อว่า “confirm”

      กำหนดชื่อ CSS Class ให้กับปุ่มลบ
    2. แทรกโค้ด JavaScript ใน HTML ดังนี้

      JavaScript สำหรับ Confirm Dialog
    3. ลองเปิดเว็บด้วย Google Chrome และ Mozilla Firefox ก็จะได้ผลลัพธ์เหมือนกันดังรูป

      Confirm Dialog ที่สร้างขึ้นด้วย Bootbox.js

    นอกจาก Confirm Dialog แล้ว ท่านสามารถดูตัวอย่าง Dialog แบบอื่นๆ ได้จากที่นี่ เพียงเท่านี้ก็จะสามารถสร้าง Dialog สวยๆ และมีความยืดหยุ่นไว้ใช้งานได้แล้ว

     

  • Unlimited multi level menu in mvc

    สวัสดีค่ะวันนี้เรามาว่ากันเรื่องเมนูกันดีกว่านะคะ คำว่าเมนู ผู้เขียนคิดว่าทุกคนต้องรู้จักแน่นอน เพราะในการพัฒนาแต่ละระบบนั้นส่วนใหญ่จะมีส่วนของงานหลายๆส่วน ทำให้มีการออกแบบหน้าจอการใช้งานหลายหน้าจอเพื่อรองรับการทำงานของระบบนั้น เมื่อส่งมอบระบบ แน่นอน!! ค่ะ ความต้องการของลูกค้าไม่หยุดแค่นั้นแน่นอน เมื่อความต้องการเพิ่ม การทำงานของหน้าจอก็เพิ่ม เมนูก็ต้องเพิ่มตามมาเช่นกัน ทำให้ผู้พัฒนาต้องไปแก้โค้ดในส่วนของเมนูทุกครั้งที่มีการเพิ่มเมนู การจัดหมวดหมูของเมนู หรือต้องการปรับเปลี่ยน path ที่ไปเรียกหน้าจอนั้นๆ แค่คิดก็ดูยุ่งยากต่อการจัดการแล้ว วันนี้ผู้เขียนจึงนำวิธีการออกแบบและพัฒนาในส่วนของเมนูที่ผู้เขียนได้ใช้พัฒนาใน MVC มาเป็นตัวอย่างให้ดูกันนะคะ สำหรับผู้อ่านที่ไม่ได้ใช้ MVC ก็สามารถนำไปปรับเปลี่ยนได้ค่ะ หลังจากพัฒนาแล้วระบบก็จะสามารถ เพิ่มเมนู หรือเปลี่ยน path ของเมนู หรือ จัดการกลุ่มของเมนู ได้โดยไม่ต้อง publish ระบบ ทุกครั้งที่มีการแก้ไข ทำให้ง่ายและไม่เสียเวลาในการจัดการเลยค่ะ เรามาเริ่มกันเลยค่ะ ผู้เขียนขอแบ่งการพัฒนาเป็นสองส่วนนะคะ คือ
    1.การพัฒนาในฐานข้อมูล : ในส่วนของการออกแบบฐานข้อมูลผู้อ่านต้องออกแบบให้มีการเก็บ id ของ parent เพื่อระบุให้รู้ว่าเมนูตัวนี้เป็นลูกของเมนูตัวไหน

    ตัวอย่างการออกแบบฐานข้อมูลของเมนู

    คอลัมภ์ คำอธิบาย
     ParentID Id ของ parent
    MenuID Id ของเมนู
    MenuName ชื่อของเมนู

    2.การพัฒนาระบบ : เป็นการพัฒนาระบบผู้เขียนจะใช้วิธีการพัฒนาแบบใช้ Recursive เข้ามาจัดการในส่วนของการแสดงเมนู โดยข้อดีของ Recursive คือเราสามารถจัดการได้ทั้งส่วนที่มีเมนูย่อย และไม่มีเมนูย่อย

    ตัวอย่างในส่วนของ Recursive ที่ใช้ใน View เพื่อการแสดงเมนู ( MVC)

    @helper GetSubMenus(IEnumerable<menutable> siteMenu, Nullable<int> parentID)

    {

        foreach (var i in Model.Where(a => a.ParentID.Equals(parentID)))

        {

            var submenu = Model.Where(a => a.ParentID.Equals(i.MenuID)).Count();

     

            <li class=”@(submenu > 0 ? “dropdown-submenu” : “dropdown”)”>

                <a href=”@(!string.IsNullOrEmpty(i.MenuLink) ? Url.Content(i.MenuLink) : “~/default)” style=”font-size:16px;”>@i.MenuName</a>

                @if (submenu > 0)

                {

                    <ul class=”dropdown-menu”>

                        @GetSubMenus(siteMenu, i.MenuID)

                        @* Recursive  Call for Populate Sub items here*@

                    </ul>

                }

            </li>

        }

    }

     

    @{

        var mymenu = @Model;

        var menuParentID = mymenu.First().ParentID;

    @if (mymenu != null && mymenu.Count() > 0)

    {

        <nav class=”navbar navbar-default”>

            <div class=”container-fluid”>

                <div class=”collapse navbar-collapse” id=”bs-example-navbar-collapse-1″>

                    <ul class=” nav navbar-nav”>

                        @GetSubMenus(mymenu, menuParentID)

                    </ul>

                </div>

            </div>

        </nav>

    }

    ตัวอย่างหน้าจอของเมนูที่เรียกใช้ Recursive เพื่อจัดการเมนู

    จากรูปจะเป็นตัวอย่างของเมนูที่มีเมนูย่อย โดยมีเมนูหลักเป็น Parent ในรูป คือ testsub2 โดยมีลูก(Child) เป็น jar2 และ jar และ jar ก็เป็น Parent โดยมี testSub เป็นลูก(Child) อีกครั้ง จะเห็นว่าไม่ว่าจะต้องการให้มีเมนูย่อยแค่ไหน Recursive ก็สามารถทำเราง่ายในการจัดการมากขึ้น โดยไม่ต้องมา copy code วางหลายๆครั้งให้ยุ่งยากอีกต่อไปค่ะ และที่สำคัญในตัวอย่างเราจะดึงข้อมูลเมนูมาจากฐานข้อมูล ไม่ว่าเราจะเพิ่มเมนู หรือเปลี่ยน path หรือเพิ่มเมนูย่อยในเมนูหลักลงลึกแค่ไหน เราก็ไม่ต้องมาเสียเวลานั่งแก้โค้ดในหน้าของเมนูอีกต่อไปแล้วนะคะ ผู้อ่านท่านใดมีการจัดเมนูหลายเมนู ลองนำไปปรับใช้กันดูนะคะ ขอบคุณค่ะ^^

     

     

     

    แหล่งอ้างอิง

    https://stackoverflow.com/questions/35326302/unlimited-multi-level-menu-in-mvc

     

     

     

  • รู้จักฟังก์ชัน Excel ตอนที่ 1 เรื่อง ตระกูลท่าน Count

    หลาย ๆ ท่านคงใช้ Excel อยู่ในชีวิตประจำวันไม่มากก็น้อย อาจจะชินตากับ Function Count กันอยู่บ่อย ๆ แต่ Function นี้ ไม่ได้มาเดี่ยว ๆ นะคะ ยังมีญาติ ๆ ในตระกูลอีกเพียบเลย มาดูกันค่ะว่า มีอะไรบ้าง และแต่ละ Function นั้นทำงานกันอย่างไรค่ะ

    1. COUNT
    2. COUNTA
    3. COUNTBLANK
    4. COUNTIF
    5. COUNTIFS

    Function ตระกูล Count หลัก ๆ ที่ผู้เขียนใช้งานจะมี 5 Function ข้างต้นนะคะ สำหรับในตอนที่ 1 นี้ ผู้เขียนจะนำเสนอ 3 ฟังก์ชันแรกก่อนก็คือ COUNT, COUNTA และ COUNTBLANK ค่ะ ส่วนอีก 2 Function สามารถติดตามต่อได้ในตอนที่ 2 นะคะ

     

    COUNT 

    สำหรับฟังก์ชันนี้ จะใช้สำหรับนับจำนวนเฉพาะตัวเลข โดยไม่นับตัวอักษรและช่องว่าง ที่อยู่ในช่วงที่เรากำหนด(range) หรือเลือกทีละค่า(value) ตามที่เราต้องการ

    รูปแบบ Function แบบ value คือ COUNT(value1, [value2],…)

    • value1 คือ ค่าข้อมูลแรกที่ต้องการนับจำนวน ในที่นี้คือใส่ทีละค่า ค่านี้จำเป็นต้องระบุ
    • value2 คือ ค่าข้อมูลที่สอง ที่ต้องการนับจำนวน ไม่จำเป็นต้องมี
    • สามารถเลือกค่าได้เรื่อย ๆ

    ตัวอย่าง

    ภาพที่ 1 การเลือกทีละค่าเพื่อนับจำนวนโดยใช้ Function Count

     

    รูปแบบ Function แบบ range คือ COUNT(value1, [value2],…)

    • value1 คือ ค่าข้อมูลแรกที่ต้องการนับจำนวนในที่นี้คือใส่เป็นช่วง การระบุคือ จุดเริ่มต้น:จุดสิ้นสุด ค่านี้จำเป็นต้องระบุ
    • value2 คือ ค่าข้อมูลที่สอง ที่ต้องการนับจำนวน ไม่จำเป็นต้องระบุ
    • สามารถเลือกค่าได้เรื่อย ๆ

    ตัวอย่าง

    ภาพที่ 2 การเลือกค่าเป็นช่วงเพื่อนับจำนวนโดยใช้ Function Count

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

    หมายเหตุ เนื่องจาก จากภาพที่ 1 และ 2 มีการเลือกค่าเท่ากับการเลือกแบบช่วงดังนั้นค่าที่ได้จะเท่ากันค่ะ

    จากผลลัพธ์ที่ได้ จะเห็นได้ว่า Function Count จะนับเฉพาะตัวเลขเท่านั้น ส่วนตัวอักษรหรือช่องว่าง จะไม่ถูกนับค่ะ

    ซึ่งจะเห็นได้ว่า ข้อมูลคำนำหน้า เกรดและคะแนน จะนับได้แค่ 0 เนื่องจากมีช่องว่างข้อมูลและข้อมูลใน Column นั้นเป็นตัวอักษรค่ะ


    COUNTA

    สำหรับฟังก์ชันนี้ จะใช้สำหรับนับจำนวนข้อมูลทั้งหมดทั้งตัวอักษรและตัวเลข แต่ไม่นับช่องว่าง ที่อยู่ในช่วงที่เรากำหนด(range) หรือเลือกทีละค่า(value) ตามที่เราต้องการดังภาพค่ะ

    รูปแบบ Function แบบ value คือ COUNTA(value1, [value2],…)

    • value1 คือ ค่าข้อมูลแรกที่ต้องการนับจำนวน จะระบุทีละค่าหรือระบุเป็นช่วงก็ได้ แต่ค่านี้จำเป็นต้องระบุ
    • value2 คือ ค่าข้อมูลที่สอง ที่ต้องการนับจำนวน ไม่จำเป็นต้องมี
    • สามารถเลือกค่าได้เรื่อย ๆ

    ตัวอย่าง

    จากผลลัพธ์ที่ได้ จะเห็นได้ว่า Function CountA จะนับข้อมูลทั้งหมด ยกเว้นช่องว่าง จะไม่ถูกนับค่ะ

    ซึ่งจะเห็นได้ว่า ข้อมูลเกรดและคะแนน จะนับได้แค่ 4 เนื่องจากมีช่องว่างข้อมูลละช่อง


    COUNTBLANK

    สำหรับฟังก์ชันนี้ จะใช้สำหรับนับจำนวนเฉพาะช่องว่าง ที่อยู่ในช่วงที่เรากำหนด(range)

    รูปแบบ Function แบบ value คือ COUNTBLANK(range)

    • range คือ ช่วงของข้อมูลที่ต้องการ

    ตัวอย่าง

    จากผลลัพธ์ที่ได้ จะเห็นได้ว่า Function CountBlank จะนับเฉพาะข้อมูลที่เป็นช่องว่างค่ะ

    ซึ่งจะเห็นว่าข้อมูลในที่นี้มีช่องเกรดและช่องคะแนน ที่มีช่องว่าง Column ละช่อง

     

    สำหรับในตอนที่ 1 ก็ขอจบลงเพียงเท่านี้ สามารถติดตาม Function COUNTIF และ COUNTIFS ต่อได้ในตอนที่ 2 ค่ะ 

  • Migration project.json to csproj format (C#)

    ในช่วงการพัฒนาของ .NET Core tooling จนถึงปัจจุบัน มี design/component หลายอย่างที่มีการเปลี่ยนแปลงในลักษณะที่ไม่ compatible กับ version ก่อนหน้า หรือยกเลิกการใช้งาน หนึ่งในนั้นก็คือ project config file ที่เริ่มต้นใช้รูปแบบ json ซึ่งอยู่ใน file ที่ชื่อ project.json แต่ปัจจุบัน เปลี่ยนมาใช้ MSBuild/csproj format

    การ migration project.json ไปสู่ .csproj format ทำได้ด้วยกันสองวิธีคือ

    • Visual Studio 2017
    • dotnet migrate command-line tool

    ทั้งสองวิธีใช้กลไกการทำงานเดียวกัน ซึ่งผลที่ได้จะเหมือนกัน

    Visual Studio 2017

    เปิด project โดยเปิด file .xproj ใน Visual Studio 2017 จะปรากฎ One-way upgrade dialog ขึ้นมาให้เลือก OK, Visual Studio จะทำการ migrate โดย file ที่ถูก migrate (project.json, global.json, .xproj) จะถูกย้ายไปสำรองไว้ใน folder Backup

    dotnet migrate

    ใช้ command-line เข้าไปที่ folder ที่เก็บ project และใช้คำสั่ง dotnet migrate ซึ่งจะทำการ migrate โดย file ที่ถูก migrate (project.json, global.json, .xproj) จะถูกย้ายไปสำรองไว้ใน folder Backup

    <Project Sdk="Microsoft.NET.Sdk">
      ... 
    </Project>
    

    ข้อแตกต่างระหว่าง project.json กับ csproj format ( อยู่ในรูปแบบ XML-based ซึ่งมี root node ระบุ sdk ที่ใช้คือ Microsoft.NET.Sdk สำหรับ web project sdk ที่ใช้คือ Microsoft.NET.Sdk.Web ) มีดังนี้

    Common options

    ****** JSON format ******
    {
      "name": "MyProjectName",
      "version": "1.0.0-alpha-*",
    
      "authors": [ "name1", "name2" ],
      "company": "PSU",
      "language": "en-US",
      "title": "My library",
      "description": "This is my library.",
      "copyright": "PSU 3000",
      "userSecretsId": "xyz123"
    }
    
    ****** csproj format ******
    <PropertyGroup>
     <AssemblyName>MyProjectName</AssemblyName>
     <PackageId>MyProjectName</PackageId>
     <VersionPrefix>1.0.0</VersionPrefix>
     <VersionSuffix>alpha</VersionSuffix>
    
     <Authors>name1;name2</Authors>
     <Company>PSU</Company>
     <NeutralLanguage>en-US</NeutralLanguage>
     <AssemblyTitle>My library</AssemblyTitle>
     <Description>This is my library.</Description>
     <Copyright>PSU 3000</Copyright>
     <UserSecretsId>xyz123</UserSecretsId>
    </PropertyGroup>
    

    frameworks

    ****** JSON format ******
    {
      "frameworks": {
        "netcoreapp1.0": {},
        "net451": {}
      }
    }
    
    ****** csproj format ******
    <PropertyGroup>
     <TargetFrameworks>netcoreapp1.0;net451</TargetFrameworks>
    </PropertyGroup>
    

    dependencies

    ****** JSON format ******
    {
      "dependencies": {
        "NETStandard.Library": "1.6.0",
        "Microsoft.AspNetCore": "1.1.0",
    
        "MyOtherProject": "1.0.0-*",
        "AnotherProject": {
          "type": "project"
        }
      }
    }
    
    ****** csproj format ******
    <PropertyGroup>
     <NetStandardImplicitPackageVersion>1.6.0</NetStandardImplicitPackageVersion> 
    </PropertyGroup>
    
    <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore" Version="1.1.0" /> 
    </ItemGroup>
    
    <ItemGroup>
     <ProjectReference Include="..\MyOtherProject\MyOtherProject.csproj" />
     <ProjectReference Include="..\AnotherProject\AnotherProject.csproj" /> 
    </ItemGroup>
    

    tools

    ****** JSON format ******
    {
      "tools": {
        "Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-*"
      }
    }
    
    ****** csproj format ******
    <ItemGroup>
     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> 
    </ItemGroup>
    

    buildOptions

    ****** JSON format ******
    {
      "buildOptions": {
        "emitEntryPoint": true
        "warningsAsErrors": true,
        "nowarn": ["CS0168", "CS0219"],
        "xmlDoc": true,
        "preserveCompilationContext": true,
        "outputName": "Different.AssemblyName",
        "debugType": "portable",
        "allowUnsafe": true,
        "define": ["TEST", "OTHERCONDITION"]
      }
    }
    
    ****** csproj format ******
    <PropertyGroup>
     <OutputType>Exe</OutputType>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <NoWarn>$(NoWarn);CS0168;CS0219</NoWarn>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>     
     <PreserveCompilationContext>true</PreserveCompilationContext> 
     <AssemblyName>Different.AssemblyName</AssemblyName>
     <DebugType>portable</DebugType> 
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks> 
     <DefineConstants>$(DefineConstants);TEST;OTHERCONDITION</DefineConstants>
    </PropertyGroup>
    

    testRunner

    ****** JSON format ******
    {
      "testRunner": "xunit",
      "dependencies": {
        "dotnet-test-xunit": ""
      }
    }
    
    ****** csproj format ******
    <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-*" />
     <PackageReference Include="xunit" Version="2.2.0-*" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-*" /> 
    </ItemGroup>
    

    อ้างอิง : https://docs.microsoft.com/en-us/dotnet/core/tools/project-json-to-csproj

  • ASP.NET API Security

    ปัจจุบันการพัฒนาโปรแกรมในรูปแบบของ API นั้นแพร่หลายมาก เนื่องจากจะทำให้โปรแกรมยืดหยุ่น สามารถพัฒนา Interface ไปในรูปแบบที่หลากหลาย ทั้ง Desktop, Mobile โดยเฉพาะการเรียก API ผ่าน http นั้น ถือว่าค่อนข้างที่จะยืดหยุ่นกับเกือบจะทุก platform ดังนั้น การรักษาความปลอดภัยให้กับ API เหล่านี้ เป็นสิ่งที่จำเป็นและสำคัญอย่างยิ่ง

    บทความนี้จะพูดถึง 2 เรื่องหลักๆ ได้แก่ การ Authentication และการ Authorization ดังนี้ครับ

     

    Authentication

    เปรียบเสมือนกับการตรวจสอบว่าใครเป็นผู้ร้องขอ (request) ซึ่งอาจจะเป็นในลักษณะของ username/password หรือเป็น API Key จากใน HTTP Request Header

    ในบทความนี้จะขอข้ามการพูดถึงการ authentication ด้วย username/password เพราะเชื่อว่าสามารถทำกันได้อยู่แล้ว ไม่ว่าจะเป็นการเขียน provider เองหรือใช้ provider ที่มีมาให้กับ .net framework ซึ่งได้แก่ MembershipProvider โดยจะขอเริ่มพูดในส่วนของ API Key ซึ่งจะใช้คลาส HttpMessageHandler (ทำงานใน http message level ดีกว่าไปทำใน controller แน่นอนครับ) วิธี implement คือ การสร้าง Class ที่ inherite มาจาก DelegatingHandler (ซึ่งมาจาก HttpMessageHandler อีกที) จะให้ override ส่วนของการตรวจสอบ HTTP Request โดยการทำ overriding method ชื่อ SendAsync และเพื่อให้ทำงานได้ จะต้องทำการ register handler ที่ Global.asax ใน Application_Start ด้วยครับ ด้วยคำสั่ง GlobalConfiguration.Configuration.MessageHandlers.Add(new MY_CLASS());

    API Key Authentication

    เราจะต้องมี API Key โดยสามารถเก็บไว้เป็นค่าคงที่ หรือเก็บไว้เป็นข้อมูลในฐานข้อมูล จากนั้นทำการตรวจสอบ Request ที่เข้ามาด้วยคำสั่งต่อไปนี้

    HttpRequestMessage.Headers.TryGetValues(“API_KEY”, out myHeader)
    (ต้องทำการสร้าง instance ของ HttpRequestMessage ก่อนนะครับ — myHeader เป็น type IEnumerable<string>)

    จากนั้นเรานำค่าในตัวแปรมาตรวจสอบกับ API Key ของเรา ที่เราเก็บไว้ เช่น

    ถ้าเก็บไว้เป็นค่าคงที่ ก็ตรวจสอบดังนี้

    myHeader.FirstOrDefault().Equals(“MySecretAPIKeyNaJa”);

    หรือถ้าเก็บไว้ในฐานข้อมูล ก็ตรวจสอบดังนี้

    db.API_KEY.Where(w => w.KEY == myHeader.FirstOrDefault()).Count() > 0 เป็นต้น

    หากเป็น API Key ที่ถูกต้อง สามารถ return response ดังนี้ได้ทันที await base.SendAsync(HttpRequestMessage, CancellationToken);
    ส่วนถ้าเป็น API Key ที่ไม่ถูกต้อง สามารถ return response ดังนี้ เพื่อให้ browser รู้ว่าเกิดอะไรขึ้น HttpRequestMessage.CreateResponse(HttpStatusCode.Forbidden, “Invalid API Key”);

     

    Authorization

    การทำ authorization นี้จะใช้งาน RoleProvider จาก .NET Framework ซึ่งจะต้องทำการ Implement role provider มาก่อน (รายละเอียด: https://msdn.microsoft.com/en-us/library/8fw7xh74.aspx)

    หลังจากการทำยืนยันตัวตน (Authentication) แล้ว ควรจะทำการตรวจสอบการอนุญาตให้เข้าถึงทรัพยากรหรือการกระทำ (action) ด้วย ด้วยการใช้งาน AuthorizeAttribute ซึ่งเป็น filter attribute ด้วยวิธีการง่ายๆ เพียงการใส่ [Authorize] ไว้บน Controller (Class) หรือ Action (Method) ซึ่งจะมี attribute [AllowAnonymous] ให้สามารถใช้ได้ สำหรับ controller, action ที่ไม่ต้องการการ authentication หรือ authorization ครับ

     

    นอกจากนี้เรายังสามารถระบุ role กับ attribute ได้อีกด้วย เช่น [Authorize{Roles = “Manager, ValidUsers”}] เป็นต้นครับ

     

     

     

  • การพัฒนา Unit Testing โดย MSTest test library (.NET Core)

    บทความนี้นำเสนอขั้นตอนการพัฒนา Unit Test อย่างง่ายโดยแสดงให่้เห็นการพัฒนาทีละขั้นตอน สำหรับระบบที่พัฒนาบน .NET Core โดยใช้ MSTest เพื่อทำความเข้าใจการพัฒนาแบบ test-driven development (TDD) ซึ่งในขั้นแรก เริ่มต้นด้วยการสร้าง source project ชื่อ “CalcTool” ใช้สำหรับการทดสอบ อยู่ภายใต้ folder “TestSample” ซึ่งขั้นตอนการสร้าง project สามารถดูได้จาก บทความ การพัฒนา Unit Testing โดย xUnit test library (.NET Core) ซึ่งจะได้ class “Calc” ซึ่งประกอบไปด้วย method “AbsAddByOne(int value)” ที่ยังไม่ได้เขียน code การทำงาน

    สร้าง test project ที่ใช้ MSTest library โดยเปิด command prompt เข้าไปที่ folder “TestSample” และสร้าง sub folder ชื่อ “CalcTool.MSTests” จากนั้นเข้าไปที่ folder “CalcTool.MSTests” ทำการสร้าง .NET Core project โดยใช้คำสั่ง

    dotnet new mstest

    โดยคำสั่งนี้จะทำการสร้าง test project ที่ใช้ MSTest test library และกำหนดค่า test runner

    <ItemGroup>
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0"/>
        <PackageReference Include="MSTest.TestAdapter" Version="1.1.11"/>
        <PackageReference Include="MSTest.TestFramework" Version="1.1.11"/>
    </ItemGroup>
    

    เพิ่ม reference ไปยัง project ที่ต้องการทดสอบซึ่งในที่นี้คือ CalcTool project โดยใช้คำสั่ง

    dotnet add reference ../CalcTool/CalcTool.csproj

    จากนั้นให้ execute คำสั่ง dotnet restore เพื่อ restore NuGet package ที่จำเป็นต้องใช้ในแต่ละ project

    เริ่มพัฒนา unit testing โดยลบ file “UnitTest1.cs” และสร้าง file ใหม่โดยใช้ชื่อว่า “CalcTest.cs” และเขียน code ดังนี้

    using System;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    
    namespace CalcTool.MSTest
    {
        [TestClass]
        public class CalcTest
        {
            [TestMethod]
            public void AbsAddByOneTest()
            {
                var c = new Calc();
                var result = c.AbsAddByOne(5);
    
                Assert.AreEqual(result, 6);
            }
        }
    }
    

    *[TestClass] attribute ใช้เพื่อบอกว่ามี unit test อยู่ใน class นั้น
    *[TestMethod] attribute ใช้เพื่อกำหนดว่า method นั้นๆเป็นแบบ single test

    ทำการทดสอบโดยการ execute dotnet test ซึ่งจะทำการ build และ start MSTest test runner ซึ่งพบว่าผลการทดสอบ fail เนื่องจากยังไม่ได้ implement code ใน method “AbsAddByOne” ของ class “Calc” ดังนั้นกลับไปที่ method “AbsAddByOne” เพิ่ม code ลงไปเพื่อให้ผลการทดสอบถูกต้อง/ผ่าน (test pass)

            public int AbsAddByOne(int value)
            {
                return Math.Abs(value) + 1;
                //throw new NotImplementedException("Not implement");
            }
    

    กลับไปที่ folder “CalcTool.MSTests” และ execute dotnet test ซึ่งจะทำการ build และ start MSTest test runner ซึ่งพบว่า ผ่านการทดสอบ (test pass)

    MSTest มี attribute ที่ใช้กำหนด suite of tests ซึ่ง execute code เดียวกันแต่มี input ที่มีค่าแตกต่างกันนั่นคือ [DataTestMethod] attribute และใช้ [DataRow] attribute ในการกำหนดค่าของ input

            [DataTestMethod]
            [DataRow(5)]
            [DataRow(-5)]
            public void AbsAddByOneTest2(int value)
            {
                var c = new Calc();
                var result = c.AbsAddByOne(value);
    
                Assert.AreEqual(result, 6);
            }
    

    การพัฒนาในรูปแบบ test-driven development (TDD) จะทำเป็นรอบๆในลักษณะนี้โดย เพิ่ม unit testing , เพิ่ม code ใหม่เข้าไป ทดสอบ unit test จนผ่านและเริ่มรอบใหม่ จนกระทั่งได้ test ชุดสุดท้ายและโปรแกรม/library ที่สมบูรณ์ ก็เป็นอันจบการพัฒนาในรูปแบบ test-driven development (TDD)

  • การพัฒนา Unit Testing โดย xUnit test library (.NET Core)

    บทความนี้นำเสนอขั้นตอนการพัฒนา Unit Test อย่างง่ายโดยแสดงให่้เห็นการพัฒนาทีละขั้นตอน สำหรับระบบที่พัฒนาบน .NET Core โดยใช้ xUnit เพื่อทำความเข้าใจการพัฒนาแบบ test-driven development (TDD) ซึ่งในขั้นแรก เริ่มต้นด้วยการสร้าง source project ที่จะใช้สำหรับการทดสอบ สร้าง folder “TestSample” และ sub folder ชื่อ “CalcTool”  จากนั้นเปิด command prompt เข้าไปที่ folder “CalcTool” เพื่อทำการสร้าง .NET Core project โดยใช้คำสั่ง

    dotnet new classlib

    rename class1.cs เป็นชื่อ calc.cs และเขียน code ดังนี้

    using System;
    
    namespace CalcTool
    {
        public class Calc
        {
            public int AbsAddByOne(int value)
            {
                throw new NotImplementedException("Not implemented");
            }
        }
    }
    

    สร้าง test project โดยกลับไปที่ folder “TestSample” และสร้าง sub folder ชื่อ “CalcTool.Tests” จากนั้นเปิด command prompt เข้าไปที่ folder “CalcTool.Tests” เพื่อทำการสร้าง .NET Core project โดยใช้คำสั่ง

    dotnet new xunit

    โดยคำสั่งนี้จะทำการสร้าง test project ที่ใช้ xunit test library และกำหนดค่า test runner ไว้ใน CalcTool.Tests.csproj

    file : CalcTool.Tests.csproj
    
    <Project Sdk="Microsoft.NET.Sdk">
    
    <PropertyGroup>
        <TargetFramework>netcoreapp1.1</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0"/>
        <PackageReference Include="xunit" Version="2.2.0"/>
        <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0"/>
    </ItemGroup>
    
    </Project>
    
    

    เพิ่ม reference ไปยัง project ที่ต้องการทดสอบซึ่งในที่นี้คือ CalcTool project โดยใช้คำสั่ง

    dotnet add reference ../CalcTool/CalcTool.csproj

    ก่อนที่จะ build CalcTool project หรือ CalcTool.Tests project ต้อง execute คำสั่ง dotnet restore เพื่อ restore NuGet package ที่จำเป็นต้องใช้ในแต่ละ project

    เริ่มพัฒนา unit testing โดยลบ file “UnitTest1.cs” และสร้าง file ใหม่โดยใช้ชื่อว่า “CalcTest.cs” และเขียน code ดังนี้

    using System;
    using Xunit;
    using CalcTool;
    
    namespace CalcTool.Tests
    {
        public class CalcTest
        {
            [Fact]
            public void AbsAddByOneTest()
            {
                var c = new Calc();
                var result = c.AbsAddByOne(5);
    
                Assert.Equal(result, 6);
            }
        }
    }
    

    *[Fact] attribute ใช้กำหนดว่า method นั้นๆเป็นแบบ single test

    ทำการทดสอบโดยการ execute dotnet test ซึ่งจะทำการ build และ start xUnit test runner ซึ่งพบว่าผลการทดสอบ fail เนื่องมาจากยังไม่ได้ implement code ใน method “AbsAddByOne” ของ class “Calc” ดังนั้นกลับไปที่ method “AbsAddByOne” เพิ่ม code ลงไปเพื่อให้ผลการทดสอบถูกต้อง/ผ่าน (test pass)

            public int AbsAddByOne(int value)
            {
                return Math.Abs(value) + 1;
                //throw new NotImplementedException("Not implement");
            }
    

    กลับไปที่ folder “CalcTool.Tests” และ execute dotnet test ซึ่งจะทำการ build และ start xUnit test runner ซึ่งพบว่า ผ่านการทดสอบ (test pass)

    ใน xUnit ยังมี attribute ที่ใช้กำหนด suite of tests ซึ่ง execute code เดียวกันแต่มี input ที่มีค่าแตกต่างกันนั่นคือ [Theory] attribute และใช้ [InlineData] attribute ในการกำหนดค่าของ input

            [Theory]
            [InlineData(5)]
            [InlineData(-5)]
            public void AbsAddByOneTest2(int value)
            {
                var c = new Calc();
                var result = c.AbsAddByOne(value);
    
                Assert.Equal(result, 6);
            }
    

    การพัฒนาในรูปแบบ test-driven development (TDD) จะทำเป็นรอบๆในลักษณะนี้โดย เพิ่ม unit testing , เพิ่ม code ใหม่เข้าไป ทดสอบ unit test จนผ่านและเริ่มรอบใหม่ จนกระทั่งได้ test ชุดสุดท้ายและโปรแกรม/library ที่สมบูรณ์ ก็เป็นอันจบการพัฒนาในรูปแบบ test-driven development (TDD)