Year: 2016

  • Script สำหรับ หาวันที่ของไฟล์ล่าสุดใน directory

    Q: ถ้าจะเขียน คำสั่ง หรือ script บน linux เพื่อหา วันที่ของ file ล่าสุดใน folder ของ user จะเขียนยังไงดี มีไอเดียไหมครับ

    A: สมมติ folder ของ user คือ /home/user/Documents

    $ ls -t /home/user/Documents
    

    จะได้ไฟล์เรียงตามลำดับวันที่/เวลาของไฟล์ โดยไฟล์ล่าสุดจะโผล่มาเป็นไฟล์แรก

    ถ้าเราต้องการไฟล์ล่าสุดแค่ไฟล์เดียว ก็สามารถใช้คำสั่ง head -1 เพื่อตัดให้เหลือไฟล์เดียวได้ ก็จะได้คำสั่งเป็น

    $ ls -t /home/user/Documents | head -1
    

    ทีนี้ จากชื่อไฟล์ที่ได้ ถ้าต้องการวันที่ เราก็สามารถใช้คำสั่ง date โดยใช้ option -r สำหรับให้มันแสดงวันที่ของไฟล์ใดๆ ตย. เช่น ต้องการรู้วันที่ ของไฟล์ /etc/passwd ก็ใช้คำสั่ง

    $ date -r /etc/passwd
    

    เอาสองอย่างนี้มาใช้งานร่วมกันได้ตามนี้ครับ

    $ date -r `ls -t /home/user/Documents | head -1`
    

    ทีนี้ ถ้าต้องการให้ format ของวันที่ออกมาตามที่เราต้องการ อย่างเช่น ให้ format เป็น yyyy-mm-dd HH:MM:SS
    ก็เพิ่ม option ให้กับคำสั่ง date ประมาณนี้ “+%Y-%m-%d %H:%M:%S”

    รวมกันทั้งหมดเป็น

    $ date -r `ls -t /home/user/Documents | head -1` "+%Y-%m-%d %H:%M:%S"
    

    Q: ถ้าจะลงลึกไปหลาย level ต้องทำอะไรเพิ่มครับ
    A: หมายถึงต้องการไฟล์ล่าสุด ไฟล์เดียว จากใน directory นั้นและ sub directory ย่อยทั้งหมดใช่ใหมครับ?
    Q: ใช่ครับ
    A: งั้น คงต้องพึ่งพาคำสั่ง find ครับ เพื่อที่ list เอาเฉพาะไฟล์ทั้งหมดออกมาก่อน เพราะคำสั่ง ls ธรรมดามันจะไล่ไปตาม directory ทีละ directory

    เริ่มจาก

    $ find /home/user/Documents
    

    มันจะ list ทุกอย่างทั้งไฟล์ และ ไดเรคตอรี่และในไดเรคตอรี่ย่อยออกมา

    เราต้องการเฉพาะไฟล์ ระบุ option -type f
    เราต้องการให้มันแสดงวันที่ของไฟล์ออกมาด้วย อันนี้ต้องพึ่งคำสั่ง ls โดยใช้ option ของ ls เป็น –full-time

    $ find /home/user/Documents -type f -exec ls --full-time {} \;
    

    โดย {} เป็นการระบุว่าให้ find ใช้คำสั่ง ls –full-time กับ output ของ find ส่วน \; เป็นตัวระบุว่า จบ option ของ คำสั่ง find แค่นี้

    output ที่ได้ จะเป็นไฟล์ “ทั้งหมด” โดยที่ในแต่ละบรรทัดจะมี ข้อมูลอย่างอื่นของไฟล์นั้นออกมาด้วย เช่น permission, owner, group, size ซึ่งเราไม่สนใจ เราตัดเอาข้อมูลที่อยู่ข้างหน้าเหล่านั้นออกไปได้ โดยใช้คำสั่ง cut โดยในกรณีนี้ใช้ space ‘ ‘ เป็นตัวแบ่ง field และ เอาข้อมูลตั้งแต่ column ที่ 6 เป็นต้นไป

    $ find /home/user/Documents -type f -exec ls --full-time {} \; | cut -f6- -d' '
    

    คราวนี้เราก็ได้ไฟล์ทั้งหมดออกมาโดยนำหน้าชื่อไฟล์ด้วยวันที่/เวลา ซึ่งเราสามารถส่งเข้าไป sort โดยให้เรียงจากหลังมาหน้า
    sort -r และ เอา output ทั้งหมดมาตัดเอาเฉพาะบรรทัดแรกโดยใช้ คำสั่ง head -1 เหมือนเดิม

    $ find /home/user/Documents -type f -exec ls --full-time {} \; cut -f6- -d' ' | sort -r | head -1
    

    ซึ่ง output ที่ได้จากคำสั่ง head อันนี้จะมี วัน/เวลาของไฟล์ นำหน้า ตามด้วยชื่อไฟล์ ซึ่งอาจจะเอาไปใช้งานได้เลย
    หรือ ถ้าต้องการเฉพาะชื่อไฟล์ เพื่อจะเอาไปทำอะไรอย่างอื่นต่อ ก็ต้องส่งไปให้คำสั่ง cut เพื่อตัด field ข้อมูลที่อยู่ด้านหน้าออก

    ขอบคุณคำถามจาก Garnet Komane ครับ

    ปล. สุดท้ายแล้วจากคำสั่งข้างบนที่ว่า แทนที่จะพิมพ์เอาบน command line แล้วไปเปลี่ยน path ที่จะให้ค้นหา เขียนมันใหม่เป็น shell script เลยจะเรียกใช้งานได้ง่ายกว่า ซึ่งจะได้ shell script ประมาณนี้ครับ

    #!/bin/bash
    
    LOCATION="$1"
    
    [ -z "$LOCATION" ] && { echo "Usage: $0 LOCATION"; exit; }
    
    FILE=$(
    find $LOCATION -type f -exec ls --full-time {} \;  |\
    cut -f6- -d' '                                     |\
    sort -r                                            |\
    head -1                                            |\
    cut -f4- -d' '
    )
    
    STAMP=$(date -r "$FILE" "+%Y-%m-%d %H:%M:%S")
    
    echo "$STAMP $FILE"
    

    หรือ ดาวน์โหลด script ได้จาก ที่นี่

  • การสร้าง Hybrid App ด้วย Ionic Framework

    ในบทความนี้จะนำขั้นตอนการพัฒนา Hybrid App อย่างง่ายด้วย Ionic Framework เพื่อเป็นแนวทางการพัฒนาแก่ผู้ที่สนใจ โดยเครื่องที่ใช้ในการทดลองเป็นปฏิบัติการ Windows 10 มีขั้นตอนการติดตั้งเครื่องมือต่าง ๆ เพื่อให้สามารถใช้ Ionic ได้ดังนี้

    1. ติดตั้ง js โดยดาวน์โหลดไฟล์ node-v4.4.7-x64.msi ได้จาก http://nodejs.org วิธีการติดตั้งก็ไม่ยุ่งยากครับ ใช้ Next Technology ได้เลย จะมีหน้าจอขั้นตอนต่าง ๆ ดังรูป
      node_js_setup
    1. ติดตั้ง Cordova และ Ionic command line tool โดยการเปิด command prompt ของ windows ขึ้นมา แล้วรันคำสั่ง
      $ npm install -g ionic
      install_ionic

    เริ่มต้นสร้าง App อย่างง่าย

    หลังจากที่เตรียมความพร้อมของเครื่องที่พัฒนาเรียบร้อยแล้ว ขั้นตอนต่อไปก็จะเป็นการสร้าง App ขึ้นมา ซึ่ง Ionic ได้เตรียม template ตั้งต้นไว้ให้เราแล้ว ได้แก่ side menu, maps, salesforce, complex-list, blank เราก็ดูว่า App ของเราสามารถใช้ template ตัวไหนได้บ้าง ก็เลือกมาใช้ได้เลย
    ionic_template
    รูปที่ 1 Ionic Template [ที่มา : http://ionicframework.com]

    ขั้นตอนการสร้าง App โดยใช้ command line tools

    1. เปิด command line ขึ้นมา แล้วกำหนด working directory เป็นที่ที่สำหรับใช้เก็บ project (ในที่นี้จะกำหนดเป็น “C:\ionic” )
      cmd
    1. สร้าง project กำหนดชื่อ myApp และใช้ tabs เป็น template ตั้งต้นโดยใช้คำสั่ง
      $ ionic start myApp tabs
      ผลลัพธ์ที่ได้จากคำสั่งนี้ จะทำให้ได้ไฟล์ต่าง ๆ ดังรูป
      file_structureจะพบว่ามีโฟลเดอร์ชื่อ www สำหรับเก็บโค้ด HTML, CSS, Java Script เหมือนกับการพัฒนาเว็บไซต์ ให้เราใช้ความรู้ด้านการเขียนเว็บไซต์สร้างไฟล์ต่าง ๆ เก็บไว้ในโฟลเดอร์นี้ได้เลย
    1. หลังจากสร้างไฟล์ App ขึ้นมาแล้ว ก็จะเข้าสู่ขั้นตอนการ Build ให้ App สามารถรันบน Platform ต่าง ๆ ได้ สำหรับในบทความนี้จะแสดงเฉพาะการ Build สำหรับ Android เพื่อให้เห็นเป็นแนวทางการพัฒนา เนื่องจากเครื่องที่ใช้ทดสอบเป็น Windows 10 หากจะ Build ให้รันบน iOS ได้ ก็ต้อง Build บนเครื่องที่ใช้ระบบปฏิบัติการ iOS ในส่วนของการเตรียมความพร้อมสำหรับการ Build ให้รันบน Android ได้ เครื่องที่ใช้ในการพัฒนาจะต้องติดตั้ง Java Development Kit (JDK) 7 และAndroid Stand-alone SDK Tools หรือ Android Studio  ก่อน ซึ่งขั้นตอนการติดตั้งก็ไม่ยากครับ ใช้ Next Technology เช่นเคย
    1. Build และ Run บน Android โดยใช้คำสั่งต่อไปนี้
      $ ionic platform add android
      $ ionic build android
      $ ionic emulate androidจะทำให้ได้ผลลัพธ์ดังรูป
      android_app
      จากรูปเป็นผลลัพธ์ที่ได้จากการใช้ Tab template ซึ่งผู้พัฒนาจะต้องแก้ไขโค้ดให้เป็นไปตามที่ได้ออกแบบไว้ ก็จะได้ Android App ตามต้องการ

     

     

     

  • ตอนที่ 4 : ใช้ Report Wizard ช่วยสร้างรายงาน

    จากตอนที่แล้วได้กล่าวถึงการสร้างรายงานว่าสามารถทำได้ 2 แบบ กล่าวคือ สร้างรายงานจากไฟล์ว่างๆ หรือ ใช้ Report Wizard เป็นตัวช่วยสร้างรายงาน ซึ่ง Wizard สามารถออกแบบรายงานเป็นTable หรือ Crosstab ตามรูปแบบที่มีให้เลือก โดยทำตามขั้นตอน ดังนี้

    1. ไปที่ Solution Explorer คลิกขวาโฟลเดอร์ Reports > Add New Report จะปรากฏหน้าจอ Report Wizard ดังรูปที่ 1

    B4_pic1

    รูปที่1 Report Wizard

    2. คลิก Next เลือก Data Sources เป็นแบบ Shared หรือ Embedded Data Sources ดังรูปที่ 2

    B4_pic2

    รูปที่2 Data Sources

    3. คลิก Next เพื่อเขียน query โดยกดปุ่ม Query Builder… จะไปยังหน้าจอ Query Designer

    B4_pic3 3-7
    รูปที่3 Design Query

    4. คลิก Next จะปรากฏหน้าจอให้เลือกว่าจะสร้างรายงานเป็นตาราง (Tabular) หรือ Metrix (สามารถสรุปข้อมูลในลักษณะ Pivot Table) ดังรูปที่ 4
    B4_pic4

    รูปที่4 Report Type

     5. คลิก Next เพื่อมาออกแบบตาราง ผู้ใช้จะต้องเลือก fields ที่อยู่ใน Available fields มาใส่ใน Displayed field ในที่นี้จะจัดกลุ่มข้อมูล (Group) ตาม CAMPUS_ID, FAC_ID, DEPT_ID และ fields อื่นๆจะแสดงในส่วนของ Details

    B4_pic5

    รูปที่5 Design Table

    6. คลิก Next เพื่อเลือกรูปแบบการจัดวางข้อมูล (Layout) และคลิกเลือก Include subtotals เพื่อรวมข้อมูลในระดับที่ได้ Group ไว้ในข้อที่แล้ว (CAMPUS_ID, FAC_ID, DEPT_ID) และคลิก Next เพื่อเลือก Style ดังรูปที่ 6

    B4_pic6B4_pic7

    รูปที่6 Table Layout & Style

    7. คลิก Next จะเห็นช่อง Report name: ให้ตั้งชื่อรายงาน และกดปุ่ม Finish จะได้รายงาน ดังรูปที่ 7

    B4_pic8

    รูปที่7 ตัวอย่างรายงาน

           จากการทดลองสร้างรายงานโดยไฟล์ว่างเปล่ากับใช้Wizard เป็นตัวช่วยสร้างรายงาน พบว่า Wizard สามารถทำให้งานง่ายขึ้นได้ ซึ่งรูปแบบที่มีให้เลือกอาจจะยังไม่ถูกใจผู้ใช้ก็ค่อยมาปรับกันในทีหลังได้ เช่น Format จำนวนเงิน, ใส่ Header / Footer, ใส่ Expression ใน Textbox… ส่วนในเรื่องการคำนวณจะเป็นการรวมข้อมูลแบบตรงไปตรงมา หากผู้ใช้ต้องการการคำนวณที่ซับซ้อนขึ้นก็สามารถทำได้ เช่น อยากได้คอลัมน์ยอดเงินสุทธิ = AMOUNT – AMOUNT_FINE – AMOUNT_VAT เป็นต้น

  • Unit test สำหรับ method ที่ใช้ Entity Framework

    กลไกการทำงานของ .NET method โดยส่วนใหญ่  จะมีกลไกการทำงานที่ต้องขึ้นกับเงื่อนไข ปัจจัยต่างๆจากภายนอกอย่างเช่น method ที่มีเรียกใช้ database หรือ web service ทำให้การทดสอบจำเป็นต้องแยก component ที่ต้องการทดสอบออกมาจากปัจจัยแวดล้อมต่างรอบๆ component ซึ่งสามารถทำได้โดยใช้ shim ที่อยู่ภายใต้การควบคุมในกระบวนการทดสอบ สามารถที่จะควบคุมผลลัพธ์ที่ได้การทำงานตามที่กำหนดในทุกๆครั้งที่เรียกใช้งาน ซึ่งทำให้การเขียน unit testing ทำได้ง่ายขึ้นมาก

    การพัฒนา unit test สำหรับ method ที่ใช้ Entity Framework เพื่อเข้าถึงฐานข้อมูล ก็สามารถใช้ shim type ในการกำหนดชุดของข้อมูลเพื่อทำการทดสอบ ซึ่งการ query จะกระทำกับ property ของ DbContext ซึ่ง return IDbSet<T>

    public partial class Entities : DbContext
    {
       public Entities(): base("name=Entities")
       {
       }
    
        public IDbSet<CONFIG> CONFIG { get; set; }
       ...
    }
    

    ในการพัฒนา unit test จะต้องสร้าง shim type สำหรับ class “Entities” และแทนที่ property ที่ดึงข้อมูลจากฐานข้อมูลจริงแยกออกจากการทดสอบ ด้วยข้อมูลสำหรับทดสอบ จากตัวอย่างข้างบนคือ property “CONFIG” ซึ่งเป็นประเภท IDbSet<CONFIG> โดยจะทำการ returm DbSet<CONFIG> ที่เตรียมข้อมูลไว้สำหรับการทดสอบ

    using (ShimsContext.Create())
    {
         ShimEntities.AllInstances.CONFIGGet =
                  (e) =>  {
                             return ... DbSet<CONFIG>
                          };
    }

    แต่ใน DbSet ไม่มี public constructor ทำให้ไม่สามารถสร้าง instance ของ DbSet ได้ จึงจำเป็นต้องสร้าง class ใหม่ที่ implement interface IDbSet<T> แทนการใช้ DbSet

    public class TestDbSet<T> : IDbSet<T>, IQueryable, IEnumerable<T>
        where T : class
     {
         ObservableCollection<T> _data;
         IQueryable _query;
    
         public TestDbSet() //: base()
         {
            _data = new ObservableCollection<T>();
            _query = _data.AsQueryable();
         }
    
         public virtual T Find(params object[] keyValues)
         {
            throw new NotImplementedException("Derive from TestDbSet<T> and override Find");
         }
    
         public T Add(T item)
         {
             _data.Add(item);
             return item;
         }
    
         public T Remove(T item)
         {
             _data.Remove(item);
             return item;
         }
    
         public T Attach(T item)
         {
             _data.Add(item);
             return item;
         }
    
         public T Create()
         {
             return Activator.CreateInstance<T>();
         }
    
         public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
         {
             return Activator.CreateInstance<TDerivedEntity>();
         }
    
         public ObservableCollection<T> Local
         {
             get { return new ObservableCollection<T>(_data); }
         }
    
         Type IQueryable.ElementType
         {
             get { return _query.ElementType; }
         }
    
         System.Linq.Expressions.Expression IQueryable.Expression
         {
             get { return _query.Expression; }
         }
    
         IQueryProvider IQueryable.Provider
         {
             get { return _query.Provider; }
         }
    
         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
         {
             return _data.GetEnumerator();
         }
    
         IEnumerator<T> IEnumerable<T>.GetEnumerator()
         {
             return _data.GetEnumerator();
         }
     }

    หลังจากนั้นสร้าง class อีกหนึ่ง class ที่ inherit มาจาก TestDbSet<T> ระบุ generic type เป็น model class ที่ต้องการในที่นี้คือ CONFIG  และเขียน code override Find() method ของTestDbSet<T> เพื่อทำหน้าที่ค้นหา object ตาม key ที่ส่งมา ( สาเหตุ ต้องสร้าง class ที่ inherit มาจาก TestDbSet<T> เนื่องจาก แต่ละ model class อาจจะมี key ที่ไม่ตรงกัน ดังนั้นใน  TestDbSet<T>.Find() method จึงไม่สามารถ implement code ที่ต้องการได้ )

     public class ConfigDbSet : TestDbSet<Models.CONFIG>
     {
        public override Models.CONFIG Find(params object[] keyValues)
        {
            return this.SingleOrDefault(s => s.ID == (decimal)keyValues.Single());
        }
     }

    จากนั้นกลับมาที่ unit test method ก็จะทำการ new ConfigDbSet() แล้วส่งข้อมูลที่สำหรับทดสอบกลับไปได้ โดยไม่ต้องใช้ข้อมูลจริงจากฐานข้อมูล

  • ทำอย่างไรให้เว็บไซต์ที่เราพัฒนาสามารถอัพโหลดไฟล์แบบคราวละหลายไฟล์ได้โดยไม่จำกัดจำนวน ด้วย ASP.NET(C#)

              ในการพัฒนาเว็บไซต์ บางครั้งอาจมีความจำเป็น หรือความต้องการจากผู้ใช้ที่ต้องการให้เว็บไซต์ดังกล่าวมีฟังก์ชั่นการทำงานในส่วนของการอัพโหลดไฟล์เพื่อแนบไฟล์เข้าไปในระบบและบันทึกลงฐานข้อมูล เพื่อให้สามารถเรียกดูข้อมูลการแนบไฟล์ดังกล่าวได้ในภายหลัง การอัพโหลดไฟล์จึงถือเป็นอีกหนึ่งฟังก์ชั่นการทำงานที่นักพัฒนาเว็บไซต์ควรทราบไว้ ซึ่งลักษณะการทำงานโดยทั่วไปส่วนใหญ่เราจะใช้ Control ที่มีเรียกว่า “FileUpload” แบบอัพโหลดครั้งละ 1 ไฟล์ และหากมีมากกว่านั้นก็จะมีการสร้างตัว FileUpload มาวางไว้ในหน้าจอเพิ่มตามจำนวนที่ต้องการแบบตายตัว เช่น หากในหน้าจอดังกล่าวต้องการสามารถให้ทำการอัพโหลดไฟล์ได้ไม่เกิน 5 ไฟล์ต่อการอัพโหลดแต่ละครั้งก็จะมีการลากคอนโทรล FileUpload มาวางไว้ในหน้าจอจำนวน 5 ตัว เพื่อให้สามารถรองรับความต้องการในการอัพโหลดไฟล์ของผู้ใช้ได้ แต่ผู้เขียนพบว่าการทำงานดังกล่าวอาจไม่รองรับความต้องการในการทำงานของผู้ใช้ที่จะเพิ่มไฟล์ได้ครั้งละหลายๆไฟล์โดยไม่จำกัดและผู้พัฒนาไม่จำเป็นต้องระบุหรือสร้างคอนโทรล FileUpload มาวางในหน้าจอในจำนวนที่ตายตัวโดยไม่จำเป็น โดยผู้ใช้สามารถคลิกปุ่มเพื่อเพิ่มจำนวนในการอัพโหลดไฟล์แต่ละครั้งได้เองเพื่อความยืดหยุ่นในการใช้งาน ในบทความนี้ ผู้เขียนจึงขอเสนอแนวทางในการพัฒนาเว็บไซต์โดยใช้ ASP.NET(C#) ในแบบที่มีการอัพโหลดไฟล์ได้คราวละหลายๆไฟล์ในแบบไม่ต้องจำกัดจำนวนคอนโทรล FileUpload ในหน้าจอโดยมีการระบุจำนวนไฟล์ที่สามารถอัพโหลดได้ในแต่ละครั้งโดยผู้พัฒนาแบบตายตัวอย่างเช่นที่กล่าวไว้ข้างต้น

              หลังจากที่ได้มีการศึกษาเพิ่มเติม ผู้เขียนพบว่าใน .NET Framework เวอร์ชั่น 4.5 นั้นจะมีการเพิ่ม Feature การทำงานในส่วนนี้ให้กับคอนโทรล FileUpload ไว้แล้วผ่าน Properties ที่เรียกว่า AllowMultiple ซึ่งจะทำให้สะดวกต่อการพัฒนาและสามารถลดปัญหาดังกล่าวข้างต้นได้ แต่สำหรับเวอร์ชั่นที่ต่ำกว่ายังคงต้องมีการปรับปรุงพัฒนาเพิ่มเติมเอง ในบทความนี้จึงขอยกตัวอย่างการพัฒนาทั้ง 2 แบบในเบื้องต้นโดยจะเน้นไปในแบบเวอร์ชั่นที่ต่ำกว่า 4.5 เพื่อให้ผู้อ่านได้นำไปเป็นแนวทางในการพัฒนาต่อไป ดังนี้
    1. การอัพโหลดไฟล์คราวละหลายๆไฟล์โดยใช้ .Net Framework เวอร์ชั่นที่ต่ำกว่า 4.5

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

    1) ออกแบบหน้าจอการทำงานในฝั่ง Client  ทำการออกแบบหน้าจอการทำงานไว้ในเบื้องต้น โดยมีการสร้างคอนโทรล FileUpload มาตั้งต้นไว้ 1 ตัว และมีปุ่มเพื่อให้ทำการเพิ่มคอนโทรล FileUpload ได้เองอัตโนมัติโดยตัวผู้ใช้เอง และปุ่มที่ใช้ในการอัพโหลดไฟล์จากคอนโทรล FileUpload ทั้งหมด ดังนี้

    <body>
    <form id="form1" runat="server">
    <div>
     <div id="fileUploadarea" class="Divborder">
      <div id='divfirstUpload'><br /><asp:FileUpload ID="fuMultiple" runat="server" CssClass="fileUpload" onchange="javascipt:FileValidate(this,5);" />&nbsp;  <input style="display:inline;" id="BtnRemove" type="button" value="Remove" onclick="DelFileUpload('divfirstUpload');" /> 
    </div> 
    </div>
    <br /> 
    <div>&nbsp; 
    <input style="display:inline; background-color: #A4EDFF; color: #333333; width: 150px; font-weight: bold;" id="btnAddMoreFiles" type="button" value="Add more files" onclick="AddMoreFilesWithMax(6);" />
    &nbsp; <asp:Button ID="BtnUpload" runat="server" onclick="BtnUpload_Click" Text="Upload" OnClientClick="return ValidateFileUpload();" BackColor="#3399FF" Font-Bold="True" ForeColor="White" Width="150px" /> 
    <br> 
    <asp:GridView ID="gvResult" runat="server" AutoGenerateColumns="False" CellPadding="4" ForeColor="#333333" GridLines="None" Width="410px">
    <RowStyle BackColor="#EFF3FB" /> 
    <Columns> 
    <asp:BoundField DataField="Name" HeaderText="ชื่อไฟล์"> 
    <ItemStyle Width="200px" /> </asp:BoundField> 
    <asp:BoundField DataField="FileSize" HeaderText="ขนาดไฟล์(KB)"> 
    <ItemStyle HorizontalAlign="Right" Width="120px" /> 
    </asp:BoundField> 
    </Columns> 
    <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> 
    <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" /> <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" /> <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> 
    <EditRowStyle BackColor="#2461BF" /> <AlternatingRowStyle BackColor="White" /> 
    </asp:GridView> 
    </div> 
    </div> 
    </form> 
    </body>

    2) ในส่วนของจาวาสคริปต์ที่ใช้เป็นตัวช่วยในการตรวจสอบ โดยจะแยกเป็นแต่ละ function ดังนี้

    2.1) ฟังก์ชั่นที่ใช้ในการเพิ่มไฟล์ได้ไม่จำกัด โดยใช้ชื่อว่า AddMoreFiles()

    <script language="javascript" type="text/javascript">
    
    function AddMoreFiles() {
    
      if (!document.getElementById && !document.createElement)
        return false;
    ////อ้างอิงถึง div ที่มีชื่อว่าfileUploadarea
      var fileUploadarea = document.getElementById("fileUploadarea"); 
      if (!fileUploadarea) return false; 
       var newLine = document.createElement("br"); fileUploadarea.appendChild(newLine); 
       var newFile = document.createElement("input"); 
       newFile.type = "file"; newFile.setAttribute("class", "fileUpload");
    
    /////กำหนดค่าเริ่มต้นให้กับตัวแปร AddMoreFiles.lastAssignedId 
    เพื่อใช้ในการกำหนด ID ให้กับคอนโทรลที่สร้างขึ้น และเป็นตัวนับ
      if (!AddMoreFiles.lastAssignedId) 
        AddMoreFiles.lastAssignedId = 1;
    
     //////กำหนดค่าแอททริบิวต์ต่างๆให้กับ FileUpload ที่ทำการสร้างใหม่ ในทีนี้คือ id และ name
        newFile.setAttribute("id", "FileUpload" + AddMoreFiles.lastAssignedId);  
        newFile.setAttribute("name", "FileUpload" + AddMoreFiles.lastAssignedId); 
    
     //////สร้าง div ขึ้นมาใหม่ และกำหนดค่าแอททริบิวต์ต่างๆให้กับ div ที่ทำการสร้างใหม่ 
    ในทีนี้คือ id และ เพิ่มคอนโทรลที่สร้าง tag ไว้ด้านบนเข้ามาไว้ใน div 
        var div = document.createElement("div"); 
        div.appendChild(newFile); 
        div.setAttribute("id", "div" + AddMoreFiles.lastAssignedId); 
    
    //////เพิ่ม div ที่สร้างใหม่ไปยัง div ที่มีชื่อว่า fileUploadarea และเพิ่มค่าของตัวแปร AddMoreFiles.lastAssignedId   
        fileUploadarea.appendChild(div); 
        AddMoreFiles.lastAssignedId++; 
    }

    2.2) ฟังก์ชั่นที่ใช้ในการเพิ่มไฟล์ได้โดยมีการระบุจำนวนสูงสุดที่ยอมให้อัพโหลดแต่ละครั้ง ในกรณีที่ต้องการระบุค่าจำนวนไฟล์ที่ต้องการให้ผู้ใช้สามารถอัพโหลดได้สูงสุดในแต่ละครั้ง สามารถปรับแก้เพิ่มเติมจากฟังก์ชั่นก่อนหน้านี้ โดยการส่งพารามิเตอร์จำนวนมากสุดที่ยอมให้สร้างตัวอัพโหลดไฟล์ได้ โดยใช้ชื่อฟังก์ชั่นว่า AddMoreFilesWithMax(x) ดังนี้

    function AddMoreFilesWithMax(x) 
    { //////กำหนดค่าให้กับ AddMoreFiles.lastAssignedId 
    เพื่อเป็นค่าตั้งต้นในการไปเซทค่า id และ name ของคอนโทรลที่สร้างใหม่ 
    โดยในที่นี้เริ่มต้นที่ 2 เนื่องจากเดิมมีคอนโทรลอัพโหลดไฟล์อยู่เดิมแล้ว 1 ตัว
      if (!AddMoreFiles.lastAssignedId)
          AddMoreFiles.lastAssignedId = 2;
    
       if (!document.getElementById && !document.createElement)
          return false; 
      var fileUploadarea = document.getElementById("fileUploadarea");
        if (!fileUploadarea) return false;
    
    /////เป็นการตรวจสอบเงื่อนไขว่ายังไม่เกินจำนวนมากสุดที่ระบุไว้ผ่านพารามิเตอร์ x
          if (AddMoreFiles.lastAssignedId <= x) 
          { 
          var newLine = document.createElement("br"); 
    
          //สร้างปุ่ม Remove เพื่อใช้ลบคอนโทรล FileUpload ที่สร้างขึ้นออกโดยผู้ใช้ 
          var newbtnDel = document.createElement("input"); 
          newbtnDel.setAttribute("id", "btnDelUpload" + AddMoreFiles.lastAssignedId);
          newbtnDel.setAttribute("name", "btnDelUpload" + AddMoreFiles.lastAssignedId); 
          newbtnDel.setAttribute("value", "Remove");   
          newbtnDel.type = "button"; 
    
         //////สร้างคอนโทรล FileUpload ขึ้นใหม่และกำหนดแอททริบิวต์ต่างๆให้
          var newFile = document.createElement("input"); 
          newFile.type = "file"; 
          newFile.setAttribute("class", "fileUpload");
          newFile.setAttribute("id", "FileUpload" + AddMoreFiles.lastAssignedId); 
          newFile.setAttribute("name", "FileUpload" + AddMoreFiles.lastAssignedId);
    
    //////เป็นส่วนของการเรียกฟังก์ชั่นในการตรวจสอบไฟล์ที่ทำการอัพโหลดว่าถูกต้องตรงตามเงื่อนไขหรือไม่
         เมื่อมีการเลือกไฟล์ใดๆจากผู้ใช้ ซึ่งจะพูดถึงในส่วนถัดไปในการสร้างฟังก์ชั่น FileValidate(ctrl,maxVal) 
          newFile.setAttribute('onchange', 'javascipt:FileValidate(this,5);');
    
    //////สร้าง div ขึ้นมาใหม่ และกำหนดค่าแอททริบิวต์ต่างๆให้กับ div ที่ทำการสร้างใหม่ 
    ในทีนี้คือ id และ เพิ่มคอนโทรลที่สร้าง tag ไว้ด้านบนเข้ามาไว้ใน div 
          var div = document.createElement("div");
          div.appendChild(newLine);
          div.appendChild(newFile);
    //////เพิ่มช่องว่างระหว่างคอนโทรลอัพโหลดไฟล์และปุ่ม Remove เพื่อความสวยงาม
          div.appendChild(document.createTextNode('\u00A0'));
          div.appendChild( newbtnDel);
          div.setAttribute("id", "div" + AddMoreFiles.lastAssignedId);
    
    //////เพิ่ม event การคลิกปุ่ม Remove เพื่อให้ไปเรียกใช้ฟังก์ชั่น DelFileUpload
    โดยส่งค่า id ของ div ที่ต้องการให้ลบออกซึ่งจะกล่าวถึงในส่วนถัดไปของการสร้างฟังก์ชั่น DelFileUpload(divid);
          newbtnDel.setAttribute('onclick', 'javascipt:DelFileUpload(\'' + "div" + AddMoreFiles.lastAssignedId + '\');');
    
          fileUploadarea.appendChild(div);
          AddMoreFiles.lastAssignedId++; } 
    
    ///////กรณีที่มีจำนวนตัวอัพโฟลดไฟล์ที่ถูกสร้างขึ้นเองเกินกว่าที่กำหนด(ค่าพารามิเตอร์ x) 
    จะแสดงข้อความให้ทราบ
     else { 
           alert("ขออภัย ท่านสามารถอัพโหลดไฟล์ได้สูงสุดครั้งละไม่เกิน " + x + " ไฟล์ต่อการอัพโหลดแต่ละครั้ง กรุณาลองใหม่อีกครั้ง"); 
          return false;
           } 
          }

    2.3) ฟังก์ชั่นที่ใช้ในการตรวจสอบขนาดของไฟล์ โดยมีการระบุขนาดสูงสุดที่ยอมให้อัพโหลดแต่ละครั้ง โดยใช้ชื่อฟังก์ชั่นว่า FileValidate(ctrlFile,MaxSize)

    ////เป็นการประกาศตัวแปรที่ใช้ในการตรวจสอบชนิดของไฟล์ที่ยอมให้อัพโหลดได้
    var _validFileExtensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png", ".pdf"];
    function FileValidate(ctrlFile,MaxSize) 
    {//////พารามิเตอร์ที่รับเข้ามาคือตัวคอนโทรลของการอัพโหลดไฟล์ที่ต้องการตรวจสอบ(ctrlFile) 
    และขนาดสูงสุดของไฟล์ที่ยอมให้อัพโหลดในแต่ละคอนโทรล(MaxSize)
    
    ////ดึงค่าชื่อของไฟล์ที่อัพโหลดที่ต้องการตรวจสอบ
      var sFileName = ctrlFile.value;
    
    /////ตรวจสอบเมื่อพบชื่อไฟล์ที่ดึง
       if (sFileName.length > 0) 
       { 
         var blnValid = false; 
    //////////ตรวจสอบนามสกุลของไฟล์ที่ดึงมาจากชื่อไฟล์กับค่าของนามสกุลไฟล์ที่ยอมให้อัพโหลด
    จากตัวแปร _validFileExtensions
         for (var j = 0; j < _validFileExtensions.length; j++) 
          { var sCurExtension = _validFileExtensions[j]; 
           if (sFileName.substr(sFileName.length - sCurExtension.length, sCurExtension.length).toLowerCase() == sCurExtension.toLowerCase()) 
           { 
             blnValid = true; 
             break; 
            } 
          }   
    //////กรณีไฟล์ดังกล่าวมีนามสกุลที่แตกต่างจากที่กำหนดจะแสดงข้อความแจ้งเตือน 
    และล้างค่าของชื่อไฟล์ในคอนโทรลอัพโหลดไฟล์ตัวดังกล่าว
         if (!blnValid) 
         { alert("ไม่สามารถอัพโหลดไฟล์ดังกล่าวได้ เนื่องจากรองรับเฉพาะไฟล์ที่มีนามสกุลดังนี้เท่านั้น: " + _validFileExtensions.join(", ")); 
          ctrlFile.value = ""; 
          return false; 
         } 
         else 
         {
    /////หากไฟล์ที่อัพโหลดมีนามสกุลไฟล์ตามที่ระบุ จะทำการตรวจสอบขนาดของไฟล์ว่าไม่เกินจากขนาดสูงสุด
    ที่กำหนดหรือไม่ โดยมีการคำนวณหน่วยเป็น MB
           var fileSize = parseFloat(ctrlFile.files[0].size / 1048576).toFixed(2);
    
    /////หากขนาดของไฟล์เกินกว่าที่กำหนดจะแสดงข้อความแจ้งเตือน และล้างค่าไฟล์ที่ต้องการอัพโหลด
    ในคอนโทรลอัพโหลดไฟล์ดังกล่าว
           if (fileSize > MaxSize) 
            { alert(" ขออภัย ขนาดของไฟล์ที่ต้องการอัพโหลดมีขนาดใหญ่เกินกว่าทีกำหนด(" + MaxSize + " MB)"); 
              ctrlFile.value = ""; 
              return false;   
           } 
         }   
        }   
      return true; 
      }

    2.4) ฟังก์ชั่นที่ใช้ในการตรวจสอบว่ามีการเลือกไฟล์ที่ต้องการอัพโหลดแล้วหรือไม่ เมื่อมีการกดปุ่ม “Upload” โดยใช้ชื่อฟังก์ชั่นว่า ValidateFileUpload() 

    function ValidateFileUpload() { 
    //////เป็นการค้นหาคอนโทรลไฟล์อัพโหลดที่มีในหน้าจอโดยใช้ tag input 
    และตรวจสอบที่มี type เป็น "file" ใส่ไว้ในตัวแปร arrInputs
        var arrInputs = document.getElementsByTagName("input");
        var blnValid; 
        var oInput;
        for (var i = 0; i < arrInputs.length; i++)
         { oInput = arrInputs[i]; 
          if (oInput.type == "file") 
          { 
           var sFileName = oInput.value; 
    
     ///////หากพบตัวอัพโหลดไฟล์ตัวใดที่ยังไม่ได้ทำการเลือกไฟล์ไว้ จะทำการแสดงข้อความแจ้งเตือนให้ทราบ
           if (sFileName.length == 0) 
             { blnValid = false; 
              alert("ขออภัย ไม่สามารถอัพโหลดไฟล์ได้ เนื่องจากพบว่ามีบางไฟล์ไม่ได้ถูกเลือก กรุณาลองใหม่อีกครั้ง"); 
              return false;   
             } 
          }   
         }   
    return true; 
    }

    2.5) ฟังก์ชั่นที่ใช้ในการลบคอนโทรลที่ใช้ในการอัพโหลดไฟล์ โดยใช้ชื่อฟังก์ชั่นว่า DelFileUpload(dv) ซึ่งจะส่งพารามิเตอร์เป็น id ของ div ตัวที่ต้องการให้ลบ และถูกเรียกใช้เมื่อมีการกดปุ่ม “Remove”

    function DelFileUpload(dv) 
    { ////ค้นหา div ตาม id ที่ส่งเข้ามา
       var elem = document.getElementById(dv); 
      ///ทำการลบ div นั้นออกจากหน้าจอและลดค่าของ AddMoreFiles.lastAssignedId ลง 1
    เพื่อใช้ในการคำนวณค่าสูงสุดและกำหนด tag ของ id และ name ของคอนโทรลที่สร้างใหม่ต่อไป
       elem.parentNode.removeChild(elem); 
       AddMoreFiles.lastAssignedId = AddMoreFiles.lastAssignedId - 1; 
    }

    3) ในฝั่งเซฺิร์ฟเวอร์ (C#)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    /////เป็น namespace ที่ต้องอ้างอิงเพิ่มเพื่อใช้ในการจัดการข้อมูลกับ datatable
    using System.Data;
    /////เป็น namespace ที่ต้องอ้างอิงเพิ่มเพื่อใช้ในการจัดการเกี่ยวกับการอัพโหลดไฟล์
    using System.IO;
    public partial class MultipleUpload : System.Web.UI.Page
    {
    protected void BtnUpload_Click(object sender, EventArgs e)
    {
      try
      {
    
     //////สมมุติเพื่อนำไปประยุกต์ใช้กับการบันทึกลงฐานข้อมูลต่อไป 
    โดยสร้างโครงสร้าง datatable ที่ชื่อว่า dtFiles 
         DataTable dtFiles = new DataTable();
         dtFiles.Columns.AddRange(new DataColumn[3] { new DataColumn("Id", typeof(int)),
         new DataColumn("Name", typeof(string)),
         new DataColumn("FileSize",typeof(string)) });
    
    ////เป็นการดึงคอนโทรลในการอัพโหลดไฟล์ที่ถูกสร้างขึ้นและวนรอบในการบันทึกไฟล์ดังกล่าว
     HttpFileCollection hfc = Request.Files;
     for (int i = 0; i < hfc.Count; i++)
     {
        HttpPostedFile hpf = hfc[i];
       if (hpf.ContentLength > 0)
        {
    //////บันทึกไฟล์ตามที่ระบุไว้
        hpf.SaveAs(Server.MapPath("~/uploads/") + System.IO.Path.GetFileName(hpf.FileName));
    
    ////เพิ่มข้อมูลลงไปใน datatable ที่สร้างไว้ สำหรับการใช้งานจริงอาจเป็นการติดต่อเพื่อบันทึกลงฐานข้อมูล 
        dtFiles.Rows.Add(i,hpf.FileName, hpf.ContentLength / 1024);
         }
       }
     if (dtFiles.Rows.Count > 0) 
     {
    //////ดึงข้อมูลจาก datatable มาแสดงในกริดวิว
        gvResult.DataSource = dtFiles;
        gvResult.DataBind();
     
     }
       }
      }
      catch (Exception)
      {
       throw;
       }
      }
    }
    

    เพิ่มเติม :
    หากต้องการตกแต่งเพื่อความสวยงามสามารถใส่ StyleSheet เพิ่มเติมได้ ดังตัวอย่างต่อไปนี้

    <style type="text/css">
      .fileUpload
      {
       width:255px;
       font-size:11px;
       color:#000000;
       border:solid;
       border-width:1px;
       border-color:#7f9db9;
       height:17px;
      }
     .Divborder
      {
       border: 2px solid;
       border-radius: 5px;
       padding:10px;
       width:390px;
      }
    </style>
    

    ตัวอย่างผลลัพธ์ที่ได้

    1) แสดงหน้าจอ โดยแรกเริ่มมีอัพโหลดไฟล์ตั้งต้น 1 ตัว

    uploadsingle

    2) แสดงหน้าจอเมื่อมีการกดปุ่ม “Add more files” uploadmulti2

    3) แสดงผลลัพธ์หลังจากบันทึกข้อมูลเรียบร้อยแล้วmultipleResult2

    2. การอัพโหลดไฟล์คราวละหลายๆไฟล์โดยใช้ .Net Framework เวอร์ชั่น 4.5  ซึ่งในบทความนี้จะไม่ลงรายละเอียดมากนัก ผู้อ่านสามารถนำไปประยุกต์เพิ่มเติมได้ โดยจะอธิบายทีละขั้นตอนคร่าวๆ ดังนี้

    1) การออกแบบในหน้าจอฝั่ง Client

    <body>
    <form id="form1" runat="server">
    <div>
    <asp:FileUpload ID="file_upload" runat="server" AllowMultiple="true" />
    <asp:Button ID="btnFileUpload" runat="server" Text="Upload" OnClick="btnFileUpload_Click" />
    <asp:Label ID="lblUploadStatus" runat="server"></asp:Label><br />
     <asp:GridView ID="gvResult" runat="server" AutoGenerateColumns="False" CellPadding="4" ForeColor="#333333" GridLines="None" Width="410px">
     <AlternatingRowStyle BackColor="White" />
     <Columns>
     <asp:BoundField DataField="Name" HeaderText="ชื่อไฟล์">
     <ItemStyle Width="300px" />
     </asp:BoundField>
     <asp:BoundField DataField="filesize" HeaderText="ขนาดไฟล์(KB)">
     <ItemStyle HorizontalAlign="Right" Width="110px" />
     </asp:BoundField>
     </Columns>
     <EditRowStyle BackColor="#2461BF" />
     <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
     <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
     <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
     <RowStyle BackColor="#EFF3FB" />
     <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
     <SortedAscendingCellStyle BackColor="#F5F7FB" />
     <SortedAscendingHeaderStyle BackColor="#6D95E1" />
     <SortedDescendingCellStyle BackColor="#E9EBEF" />
     <SortedDescendingHeaderStyle BackColor="#4870BE" />
     </asp:GridView>
    </div>
    </form>
    </body>

    2) การพัฒนาในฝั่งเซิร์ฟเวอร์(C#)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.IO;
    using System.Data;
    namespace WebAppTest
    {
     public partial class MultipleUpload : System.Web.UI.Page
     {
     protected void btnFileUpload_Click(object sender, EventArgs e)
     {
     try
     {
    ///////การตรวจสอบขนาดไฟล์และไฟล์ต้องเป็นชนิด image/jpeg เท่านั้น
     if (file_upload.HasFile && file_upload.PostedFiles.All(x => x.ContentType == "image/jpeg" && x.ContentLength < 102400))
     {
    ///////ประกาศตัวแปร และกำหนดโครงสร้างของ datatable
     int i = 1;
     DataTable dtFiles = new DataTable();
     dtFiles.Columns.AddRange(new DataColumn[3] { new DataColumn("Id", typeof(int)),
     new DataColumn("Name", typeof(string)),
     new DataColumn("FileSize",typeof(string)) });
     foreach (var file in file_upload.PostedFiles)
     {
    //////บันทึกไฟล์ที่เลือก
       file_upload.SaveAs(Server.MapPath("~/Fileupload/") + Path.GetFileName(file.FileName));
    ////วนบันทึกข้อมูลไฟล์ที่อัพโหลดลงใน datatable  
        dtFiles.Rows.Add(i, file.FileName, file.ContentLength / 1024);
     }
    
    ////แสดงข้อความเมื่อการอัพโหลดเสร็จสมบูรณ์
       lblUploadStatus.Text = "บันทึกสำเร็จ";
    
    ////แสดงค่าที่ได้ลงในกริดวิว
     if (dtFiles.Rows.Count > 0)
     {
     gvResult.DataSource = dtFiles;
     gvResult.DataBind();
    
     }
     }
     else
     {
    ////แสดงข้อความแจ้งเตือนกรณีเกิดปัญหาไฟล์มีขนาดใหญ๋เกินกว่าที่กำหนดและไม่ใช่ชนิด image/jpeg
       lblUploadStatus.Text = "กรุณาเลือกไฟล์ที่ต้องการอัพโหลดให้เหมาะสม";
     }
     }
     catch (Exception ex)
     {
    ////แสดงข้อความแจ้งเตือนกรณีเกิดปัญหาในการอัพโหลดไฟล์
       lblUploadStatus.Text = "เกิดข้อผิดพลาดในการอัพโหลดไฟล์ :" + ex.Message;
     }
     }
     }
    }

    ตัวอย่างผลลัพธ์ที่ได้

    1) ก่อนทำการอัพโหลดไฟล์

    multiple4_5_0

    2) หลังอัพโหลดไฟล์ทั้งหมดเรียบร้อยแล้ว

    multiple4_5

    หมายเหตุ : หากกำหนดค่าให้กับ Properties ที่ชื่อว่า AllowMultiple=”false ตอนเลือกไฟล์ที่จะอัพโหลดจะสามารถเลือกได้เพียงไฟล์เดียวเท่านั้น

              จากบทความและตัวอย่างข้างต้น จะเห็นว่า ลักษณะผลลัพธ์ที่ได้จะคล้ายคลึงกัน แต่จะแตกต่างกันในส่วนของความซับซ้อนในการตรวจสอบความถูกต้องของข้อมูลไฟล์แนบที่รับเข้ามา ซึ่งผู้พัฒนาเองสามารถเลือกใช้วิธีที่ตนถนัดและขึ้นกับเวอร์ชั่นของ .NET Framework ที่กำลังพัฒนา รวมถึงเงื่อนไขของการตรวจสอบให้ตรงตามความต้องการของผู้ใช้มากที่สุด โดยเนื้อหาในบทความนี้เป็นเพียงแนวทางหนึ่งให้กับท่านในเบื้องต้นเท่านั้น ท่านสามารถนำไปประยุกต์ต่อยอดเพิ่มเติมได้ และผู้เขียนหวังเป็นอย่างยิ่งว่าบทความนี้จะเป็นประโยชน์กับผู้พัฒนาที่กำลังค้นหาวิธีการอัพโหลดไฟล์คราวละหลายไฟล์นี้อยู่เช่นกัน หากมีเนื้อหาส่วนใดผิดพลาด ผู้เขียนขออภัยไว้ ณ ที่นี้ด้วย ขอบคุณค่ะ ^^
    แหล่งข้อมูลอ้างอิง :
    http://www.aspsnippets.com/Articles/Uploading-Multiple-Files-using-JavaScript-Dynamic-FileUpload-Controls-in-ASP.Net.aspx
    http://www.codeproject.com/Articles/667604/Upload-multiple-files-in-asp-net
    http://www.c-sharpcorner.com/UploadFile/99bb20/upload-multiple-files-using-fileupload-control-in-Asp-Net-4/

  • สร้าง Shortcut ในการ Remote Desktop Connection

    ในการพัฒนาระบบงานหลาย ๆ ระบบ ซึ่งแต่ละระบบก็ต้องมี Server ถ้าระบบมีปัญหาสิ่งแรก ๆ ที่ต้องเข้าไปดูปัญหานั่นคือการ Remote ไปยัง Server นั้น ๆ เช่นดู Process Task ต่าง ๆ มีอะไรรันอยู่ มีอะไรทำงานอยู่จึงทำให้ระบบช้า หรือทำให้ระบบมีปัญหาเป็นต้น เมื่อมีหลาย Server ครั้นจะมาจดจำ IP เครื่องที่จะ Remote ไป หรือบางเครื่องที่นาน ๆ จะเกิดปัญหาที ทางออกของปัญหามีค่ะ แค่เพียงสร้างเป็น Shortcut ในการ Remote Desktop Connection นั่นเอง มีขั้นตอนดังนี้

    1. ที่หน้าจอ Desktop คลิกขวา New
    2. เลือกหัวข้อ Shortcut
    3. พิมพ์ข้อความดังนี้ C:\Windows\System32\mstsc.exe /v:192.168.100.170 /w:800 /h:600
      –> 3.1 /v:ต่อด้วย IP Address:Port
      –> 3.2 /w:ต่อด้วยขนาดหน้าจอ ความกว้าง
      –> 3.3 /h:ต้อด้วยขนาดหน้าจอ ความสูง
      –> 3.4 อื่น ๆ เช่น /f หมายถึงเต็มหน้าจอ
      /admin หมายถึง Remote เข้าด้วย Session Admin13-7-2559 13-44-06
    4. คลิกปุ่ม Next
    5. ตั้งชื่อ Shortcut
      13-7-2559 13-46-33
    6. คลิก Finish จะเห็น Icon ของ Shortcut ที่ใช้ในการ Remote เพื่อเข้า Server ตามที่ได้สร้างไว้ที่หน้า Desktop
  • การพัฒนา unit test โดย shim type

    Shims เป็นหนึ่งใน technology ที่อยู่ใน Microsoft Fakes Framework ใช้ในการพัฒนา unit testing เพื่อแยก component ที่ต้องการทดสอบออกมาจากปัจจัยแวดล้อมต่างรอบๆ component ในกระบวนการทดสอบ โดย shims จะทำการเปลี่ยนทิศทางการเรียกใช้ method ที่กำหนด ไปยัง code ที่เขียนขึ้นมาใช้ในการทดสอบ ส่วนใหญ่เราจะใช้ shims เพื่อแยก component ที่ต้องการทดสอบออกจาก assemblies ที่ไม่ได้เป็นส่วนหนึ่งของ solution ในการพัฒนา (กรณีที่ต้องการแยก component ที่ต้องการทดสอบออกจาก solution ของตัวเอง ควรจะใช้ stubs )

    method ที่พัฒนาส่วนใหญ่จะ return ผลการทำงานที่ต้องขึ้นกับเงื่อนไข ปัจจัยต่างๆจากภายนอก ในทางกลับกันสำหรับ shim  shim จะอยู่ภายใต้การควบคุมในกระบวนการทดสอบ สามารถที่จะ return ผลการทำงานตามที่กำหนดในทุกๆครั้งที่เรียกใช้งาน ซึ่งทำให้การเขียน unit testing ทำได้ง่ายขึ้นมาก

    ตัวอย่าง method การตรวจสอบวันที่เอกสารในปีงบประมาณ

    public static class Utility {
        public static bool IsInFiscalYear() {
            if (DateTime.Now < new DateTime(2015, 10, 1))
                return false;
            else
                return true;
        }
    }

    เมื่อต้องการทดสอบ method “IsInFiscalYear” จะพบว่าการทำงานของ method ขึ้นอยู่กับ DateTime.Now ซึ่งเป็นเวลาปัจุจบันที่ได้จากระบบ ซึ่งทำให้การทดสอบยุ่งยากขึ้น (เมื่อทำการทดสอบต้องเปลี่ยน DateTime เพื่อทำการทดสอบ ซึ่งอาจจะกระทบกับส่วนอื่นๆ ไม่สามารถทำการทดสอบแบบอัตโนมัติได้ ) ซึ่งการทดสอบ method ที่มีการเรียกใช้ database, web service ก็เช่นเดียวกัน เนื่องจากกลไกการทำงานขึ้นอยู่กับปัจจัยภายนอก ซึ่ง shim จะเข้ามาช่วยตรงจุดนี้

    Shim types จะให้กลไกการเปลี่ยนทิศทางการเรียกใช้ .NET method ไปยัง function ( หรือ user delegate) ที่เขียนขึ้น โดย shim types จะถูกสร้างโดย Fakes generator

    using (ShimsContext.Create()
    {
        ShimDateTime.NowGet = () => new DateTime(2016, 6, 1);
        var isIn = Utility.IsInFiscalYear();
    
        Assert.AreEqual(true, isIn);
    }

    ShimDateTime คือ shim type ที่ถูกสร้างโดย Fakes generator เพื่อใช้ในการกำหนดกลไกการทำงานแทน DateTime.Now

    การเพิ่ม Fakes Assemblies ใน solution ทำได้โดย

    1. ใน solution explorer ขยาย References ของ unit test project
    2. เลือก assembly ที่มี class ที่ต้องการสร้าง shim type (จากตัวอย่างนี้ต้องการสร้าง shim type ของ DateTime ให้เลือก System.dll)
    3. click ขวา เลือก Add Fakes Assembly

    ในการใช้ shim type ใน unit test framework จะต้องเขียน test code อยู่ใน ShimsContext เพื่อควบคุม lifetime ของ shim type (ถ้าไม่อยู่ภายใต้ ShimsContext, shim type จะคงอยู่จนกระทั่งปิดโปรแกรม) การสร้าง ShimsContext ทำได้โดยการเรียกใช้งาน static Create() ดังเช่นตัวอย่าง code ข้างต้น

    shim type สามารถใช้งานแทนที่ .NET method รวมทั้ง static method, instance method

    Static methods

    ShimMyClass.MyStaticMethod = () =>5;

    Instance methods สำหรับทุก instance

    ShimMyClass.AllInstances.MyMethod = () => 5;

    Instance methods แต่ละ instance

    var myClass1 = new ShimMyClass()
    {
        MyMethod = () => 5
    };

     

    อ้างอิง :

    • https://msdn.microsoft.com/en-us/library/hh549176.aspx
  • Auto remove schema in EDMX on build

    Entity Framework (EF)  คือ data access technology ที่เริ่มเปิดตัวครั้งแรกเป็นส่วนหนึ่งของ .NET Framework 3.5 SP1 โดยตัว EF จะทำหน้าที่เป็น object-relational mapper ที่ทำให้ผู้พัฒนาไม่จำเป็นต้องเขียน code ในส่วน data access ก็สามารถใช้ข้อมูลจาก relational database โดยผ่าน object model

    การพัฒนาโปรแกรมโดยใช้ EF นั้นจำเป็นต้องมี Entity Data Model เป็น model ที่กำหนดรายละเอียดเกี่ยวกับ entity และ relationship ระหว่าง entity นั้นๆ การสร้าง Entity Data Model สามารถแยกออกเป็น 2 แนวทางคือ “Code First” เป็นการกำหนดรูปร่างของ model โดยการสร้าง class (เขียน code) จะมี database หรือไม่มีอยู่ก่อนก็ได้  และ “Database First” ที่จะทำการสร้าง model ( reverse engineer) จาก database ที่มีอยู่โดย EF Designer ซึ่ง model ที่ได้จะเก็บอยู่ใน EDMX file (.edmx) สามารถเปิดหรือแก้ไขเพิ่มเติมได้ด้วย EF Designer สำหรับ class ที่ใช้ในโปรแกรมจะถูกสร้างโดยอัตโนมัติจาก EDMX file

    ข้อมูล Entity Data Model ใน EDMX file อยู่ในรูปแบบ xml สามารถแบ่งออกเป็น 3 ส่วนคือ Storage model, Conceptual model และ Mapping ซึ่งในส่วนของ Storage model จะเป็นข้อมูลรายละเอียดของ entity จาก database เช่น

    ข้อมูล EntityType ที่ให้รายละเอียดของชื่อของ entity (table ใน database), ชื่อและประเภทของ property (column ของ table ใน database)

     <EntityType Name="VF_CONFIG_REPORT">
      <Key>
        <PropertyRef Name="ID" />
      </Key>
        <Property Name="ID" Type="number" Precision="38" Scale="0" Nullable="false" />
        <Property Name="REPORT_NAME" Type="varchar2" MaxLength="512" />
        <Property Name="REPORT_PATH" Type="varchar2" MaxLength="512" />
        <Property Name="GROUP_TYPE" Type="number" Precision="38" Scale="0" />
        <Property Name="SIGN_NUM" Type="number" Precision="38" Scale="0" />
        <Property Name="SIGNS" Type="varchar2" MaxLength="128" />
     </EntityType>

    ข้อมูล EntitySet ที่ประกอบด้วย ชื่อ,ประเภทของ entity, schema และ query ที่ใช้ดึงข้อมูล

    <EntitySet Name="VF_CONFIG_REPORT" EntityType="Self.VF_CONFIG_REPORT" store:Type="Views" store:Schema="FINANCE">
       <DefiningQuery>
          SELECT 
           "VF_CONFIG_REPORT"."ID" AS "ID",
           "VF_CONFIG_REPORT"."REPORT_NAME" AS "REPORT_NAME", 
           "VF_CONFIG_REPORT"."REPORT_PATH" AS "REPORT_PATH", 
           "VF_CONFIG_REPORT"."GROUP_TYPE" AS "GROUP_TYPE", 
           "VF_CONFIG_REPORT"."SIGN_NUM" AS "SIGN_NUM", 
           "VF_CONFIG_REPORT"."SIGNS" AS "SIGNS"
         FROM "FINANCE"."VF_CONFIG_REPORT" "VF_CONFIG_REPORT"   
       </DefiningQuery>
    </EntitySet>

    เมื่อมีการระบุ schema ของ entityใน EDMX file  นั่นทำให้การ deploy ระบบ(โปรแกรมและ database) จำเป็นต้องมี database ที่มี schema ชื่อเดียวกับที่กำหนดใน EDMX file เท่านั้น(schema ได้มาจากการ generate ของ EF Designer ในขั้นตอนการพัฒนาระบบ) ถ้าต้องการให้ EF ทำงานกับ database schema อื่นจะต้องแก้ไข schema ใน EDMX file ให้ตรงกัน หรือไม่ระบุ schema โดยลบส่วนที่ระบุ schema ออก ซึ่งการแก้ไขจะต้องทำการแก้ไขโดยตรงไปที่ EDMX file แล้วทำการ build ใหม่ (ในกรณีที่ไม่ได้เลือก build EDMX file เป็นแบบ embeded resource สามารถแก้ไขที่ .ssdl file ได้โดยไม่ต้อง build ใหม่)

    ในการเปลี่ยน schema ใน EDMX file นั้นจะต้องแก้ทุก EntitySet ที่มี ทำให้มีความเสี่ยงที่จะเกิดความผิดพลาดในการแก้ไข ทำให้ระบบไม่สามารถทำงานได้ และถ้ามีความจำเป็นต้องปรับ Entity Data Model เพื่อเพิ่ม, แก้ไข หรือลบ entity ใดๆ EF Designer จะทำการ update .EDMX file ใหม่ ทำให้ schema ที่แก้ไขไปแล้วกลับมาเหมือนเดิม ต้องเปลี่ยน schema ใหม่อีกครั้ง ก็ยิ่งจะเพิ่มความเสี่ยงที่จะเกิดความผิดพลาด และยุ่งยากในการบริการจัดการ source code

    เราสามารถทำให้กระบวนการแก้ไขหรือลบ schema ใน EDMX file เป็นไปโดยอัตโนมัติ โดยการแก้ใข .csproj เพิ่มกระบวนการแก้ไขหรือลบ schema เข้าไปในขั้นตอนการ build ของ MsBuild หลังจากกระบวนการ “EntityDeployEmbededResource” ของ EF ดังนี้

     

    <Target Name="RemoveSchemaEntityDeployEmbeddedResources" AfterTargets="EntityDeployEmbeddedResources" Condition="'@(EntityDeployEmbeddingItems)' != ''">
      <PropertyGroup>
        <RemoveSchemaEmbeddedResources>"Libs\EFRemoveSchema" $(EntityDeployIntermediateResourcePath)%(EntityDeployEmbeddedResources.EntityDeployRelativeDir)</RemoveSchemaEmbeddedResources>
      </PropertyGroup>
      <Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="$(RemoveSchemaEmbeddedResources)" />
    </Target>

    “Libs\EFRomoveSchema” เป็นโปรแกรมเล็กๆที่พัฒนาเพื่อลบ schema ใน Entity Data Model ที่อยู่ใน folder   $(EntityDeployIntermediateResourcePath)%(EntityDeployEmbeddedResources.EntityDeployRelativeDir) โดยใช้ เทคนิคการค้นหา attribute ของ node ที่ต้องการใน XML file (EDMX file) เพื่อลบ และบันทึกกลับลงไปที่ XML file นั้นๆ

     

    อ้างอิง : https://msdn.microsoft.com/en-us/data/ee712907

  • ทำความรู้จักและเรียนรู้การใช้งานเบื้องต้นกับ StringBuilder ใน .NET Framework(C#)

              โดยปกติแล้วนั้น ผู้พัฒนาโปรแกรมโดยใช้ .NET Framework มักจัดการข้อมูลที่เป็นอักษรหรือข้อความ (String) ด้วยคลาสของ String ที่มีใน .NET Framework ซึ่งประกอบไปด้วยฟังก์ชั่นหรือเมธอดที่หลากหลายที่ติดมากับตัวคลาส เพื่อเตรียมมาไว้ให้ใช้งาน และสามารถรองรับความต้องการในการจัดการข้อมูลของผู้ใช้แต่ละคน ซึ่งมีวิธีการใช้งานง่าย ไม่ยุ่งยากซับซ้อน มีมาตรฐานการใช้งานที่รู้จักโดยทั่วถึงกัน สะดวกและรวดเร็วทำให้ผู้พัฒนาสามารถเลือกวิธีจัดการข้อมูลได้อย่างมีประสิทธิภาพ อย่างไรก็ตาม แม้ว่าคลาสของ String จะมีเมธอดให้เราได้เลือกใช้กันอย่างมากมายและมีประสิทธิภาพอยู่แล้ว แต่ปัญหาอย่างหนึ่งที่พบในการใช้งานกับตัวแปรของคลาส String คือ การเชื่อมต่อหรือเปลี่ยนแปลงแก้ไขค่าข้อความของตัวแปรชนิด String ในแต่ละครั้ง จะไม่สามารถเปลี่ยนรูป หรือกลับไปแก้ไขค่าของตัวแปรบนพื้นที่หน่วยความจำเดิมที่ถูกจองไว้ให้กับตัวแปรได้ หรืออาจพูดในทางโปรแกรมแบบง่ายๆได้ว่า หากเราต้องการเปลี่ยนแปลงค่าของตัวแปรชนิด String เราจะไม่สามารถกลับไปแก้ไขค่าใน object ของตัวแปรที่ถูกสร้างขึ้นเดิมในหน่วยความจำ หรือ Memory ที่สร้างไว้ในตอนแรกได้ แต่จะมีการสร้าง object ตัวใหม่ขึ้นมา ทุกครั้งที่มีการแก้ไข/จัดการข้อมูลค่า หรือมีการใช้งานเมธอดในคลาส System.String และใช้วิธีให้ pointer ของตัวแปรชี้ไปยังตำแหน่งของ object ตัวใหม่ที่มีค่าของตัวแปรที่ถูกแก้ไขภายหลังแทน ซึ่งหากมีกรณีที่มีการเปลี่ยนแปลงค่าข้อมูล หรือเชื่อมต่อข้อความในตัวแปรดังกล่าวจำนวนหลายครั้ง หรือมีการวนลูปซ้ำในการเปลี่ยนแปลงค่าเป็นจำนวนมาก จะถือเป็นการใช้งานทรัพยากรหน่วยความจำอย่างสิ้นเปลือง เนื่องจาก object ของตัวแปรจะถูกสร้างขึ้นใหม่เรื่อยๆและมีการจองพื้นที่ให้กับ object ตัวที่ถูกสร้างขึ้นใหม่ตามจำนวนครั้งที่ทำการแก้ไขหรือเชื่อมต่อข้อความนั่นเอง ดังภาพ


    ภาพการจองพื้นที่ในหน่วยความจำของตัวแปรชนิด String
    [ที่มาของภาพ : http://www.tutorialsteacher.com/csharp/csharp-stringbuilder]

                        จากภาพตัวอย่างจะเห็นได้ว่า เดิมทีมีการกำหนดค่าให้กับตัวแปรเป็นข้อความ “Hello World!!” แต่เมื่อมีการปรับแก้ค่าของตัวแปร จะมีการสร้าง object ตัวใหม่ โดยจะเก็บค่าที่มีการเปลี่ยนแปลงของข้อความไปเป็น “Hello World!! From Tutorials Teacher” และเลื่อนตำแหน่งของ pointer ของตัวแปรที่จะชี้ไปเพื่อให้ได้ค่าใหม่นั่นเอง
              จากปัญหาดังกล่าว .NET Framework ก็ได้จัดเตรียมคลาสที่มีชื่อว่า “StringBuilder” ขึ้นมา ซึ่งเป็นคลาสที่ใช้ในการจัดการกับข้อมูลชนิดข้อความเช่นเดียวกับคลาส String โดยยินยอมให้มีการแก้ไขและปรับเปลี่ยนค่าในตัวแปรข้อความ(string) ดังกล่าวได้ใน object ตัวเดิมบนพื้นที่หน่วยความจำเดิม โดยไม่ต้องสร้าง object และทำการจองพื้นที่หน่วยความจำขึ้นใหม่ทุกครั้งที่มีการเปลี่ยนแปลงค่าของข้อความดังเช่นในคลาส String นั่นเอง ซึ่งจะทำให้สามารถประหยัดการใช้ทรัพยากรหน่วยความจำได้ในกรณีที่มีการเชื่อมต่อข้อความหรือเปลี่ยนแปลงค่าของตัวแปรจำนวนหลายครั้งได้

     ภาพการจองพื้นที่ในหน่วยความจำของตัวแปรชนิด StringBuilder

    [ที่มาของภาพ : http://www.tutorialsteacher.com/csharp/csharp-stringbuilder]

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

     

    หลักการทำงานและการจองพื้นที่ในหน่วยความจำของตัวแปรชนิด StringBuilder

              โดยปกติแล้วนั้น ค่าของ “StringBuilder.Length” จะเป็นค่าของจำนวนตัวอักษรที่มีในตัวแปร object ของ StringBuilder และจะถูกเพิ่มขึ้นเรื่อยๆ เมื่อมีการเพิ่มตัวอักษรหรืออักขระเข้าไปในตัวแปรนั้นโดยไม่มีการจองพื้นที่หน่วยความจำเพิ่ม จนกว่าค่าของ Length เท่ากับจำนวนของความจุที่จองพื้นที่หน่วยความจำไว้ ซึ่งก็หมายถึงค่าของ “Capacity” นั่นเอง และหากการเพิ่มตัวอักษรดังกล่าวทำให้ค่าของ Length มากกว่าค่าของ Capacity ใน object นั้นๆ จะมีการจองพื้นที่หน่วยความจำเพิ่มเป็นเท่าตัว เช่น จากเดิม 16 ตัวอักษรจะถูกเพิ่มเป็น 32 ตัวอักษร โดยจะสามารถเปลี่ยนแปลงได้ไม่เกินค่าความจุสูงสุด หรือที่เรียกว่า “MaxCapacity” ซึ่งถ้าหากมีการเพิ่มตัวอักษรที่เกินค่าของ MaxCapacity แล้วนั้นจะทำให้เกิดข้อผิดพลาดได้ แต่หากไม่มีการกำหนดค่าให้กับ Capacity และ MaxCapacity แล้วนั้น ค่าตั้งต้นของ Capactity เริ่มต้นจะอยู่ที่ 16 ตัวอักษร และค่าของ MaxCapacity จะอยู่ที่ประมาณ 2 พันล้านตัวอักษร หรือเทียบเท่ากับค่าสูงสุดของ Int32.MaxValue นั่นเอง

    การอ้างอิง Namespace
    โดย Namespace ที่ต้องอ้างอิงเพิ่มเติมในการใช้งานคลาส StringBuilder มีดังนี้

    • using System;
    • using System.Text;

    การประกาศตัวแปรของคลาส StringBuilder
    ในการประกาศตัวแปร object ของคลาส StringBuilder จะใช้หลักการเดียวกันกับการประกาศตัวแปร object ของคลาสโดยทั่วไป แต่สามารถประกาศโดยมีการระบุค่าอื่นเพิ่มเติมโดยใช้ Constructor ได้ ซึ่งจะขอยกตัวอย่างในกรณีที่มีการใช้งานกันโดยทั่วไป ดังนี้
    แบบเดียวกับการประกาศ object ของคลาสทั่วไป
    ตัวอย่าง

    StringBuilder sb = new StringBuilder();

    แบบมีการกำหนดค่าตั้งต้น
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!!");

    คำอธิบาย : เป็นการสร้างตัวแปร object แบบมีการกำหนดค่าเริ่มต้นให้มีค่าเท่ากับ Hello World!!
    แบบมีการระบุขนาดในการจองพื้นที่ของหน่วยความจำ(Capacity)  แม้ว่าตัวแปรของคลาส StringBuilder สามารถเพิ่มและขยายได้ไม่จำกัดโดยไม่ต้องสร้าง object ตัวใหม่ แต่ผู้พัฒนาสามารถระบุขนาดสูงสุดของจำนวนตัวอักษรที่ตัวแปรจะสามารถรองรับได้ โดยค่าดังกล่าวที่ระบุนี้ เรียกว่า “Capacity” และเรียกค่าของความยาวตัวอักษรที่มีในตัวแปรนั้นๆ โดยใช้ properties ที่มีชื่อว่า “Length” โดยหากมีการกำหนดค่าของ Capacity ให้กับตัวแปรคลาส StringBuilder เมื่อมีการแก้ไขค่าของตัวแปรจะไม่ถูกจองพื้นที่ใหม่จนกว่าความยาวของตัวอักษรจะถึงค่าของ Capacity ที่กำหนด จึงจะมีการจองพื้นที่ใหม่ให้อัตโนมัติในขนาดเดียวกับ Capacity ที่กำหนดไว้เดิมเป็นเท่าตัว แต่หากไม่ได้ทำการกำหนดค่าของ Capacity ไว้ จะมีค่า default เท่ากับ 16 ซึ่งสามารถกำหนดค่าของ Capacity ได้ดังนี้

    StringBuilder sb = new StringBuilder(25);

    คำอธิบาย : เป็นการสร้างตัวแปร object แบบมีการกำหนดขนาดของตัวแปรให้มีขนาดความจุ หรือ Capacity ไว้ที่ 25 ตัวอักษร แต่หากมีการกำหนดค่าของข้อมูลที่มีความยาวตัวอักษรมากกว่าขนาดที่กำหนด จะมีการขยายพื้นที่ความจุให้กับขนาดตัวแปรอัตโนมัติ นอกจากจะสามารถกำหนดขนาดของตัวแปร StringBuilder โดยใช้ Constructor แล้วนั้นยังสามารถระบุผ่าน Properties ที่มีชื่อว่า Capacityได้อีกด้วย ดังนี้

    sb.Capacity = 25;

    แบบกำหนดค่าเริ่มต้นและระบุขนาดความจุของตัวแปร
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!", 25);

    คำอธิบาย : เป็นการประกาศตัวแปรขนาด 25 ตัวอักษร และมีค่าเริ่มต้นเป็นคำว่า “Hello World!!”

    แบบกำหนดค่าขนาดความจุและความจุสูงสุดของพื้นที่หน่วยความจำให้กับตัวแปร
    ตัวอย่าง

    StringBuilder sb = new StringBuilder( 25,200);

    คำอธิบาย : เป็นจองพื้นที่ในการประกาศตัวแปร(Capacity)ขนาด 25 ตัวอักษร และมีค่าความจุสูงสุด(MaxCapacity)ได้ไม่เกิน 200 ตัวอักษร

    แบบกำหนดค่าเริ่มต้นที่มีการตัดข้อความ(substring) และระบุขนาดความจุของตัวแปร
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!",0,5, 25);

    คำอธิบาย : เป็นจองพื้นที่ในการประกาศตัวแปรขนาด 25 ตัวอักษร และมีค่าเริ่มต้นเป็นคำว่า “Hello”

    เนื่องจากมีการ substring ค่าข้อความ “Hello World!!” ตั้งแต่ตำแหน่งที่ 0 มา 5 ตัวอักษร จึงกลายเป็นคำว่า “Hello” นั่นเอง

    เมธอดที่จำเป็นและควรรู้ในการใช้งานคลาส String builder

    โดยแบ่งตามลักษณะการทำงาน เพื่อง่ายต่อความเข้าใจและการนำไปใช้ ดังนี้

    1.การเชื่อมต่อข้อความ เป็นการเชื่อมต่อข้อความกับค่าตัวแปรที่มีอยู่เดิมที่ตำแหน่งท้ายสุด ซึ่งมีเมธอดที่ใช้งานกันบ่อย ดังนี้
    • Append() เป็นเมธอดที่ใช้ในการเชื่อมข้อความ ซึ่งสามารถใช้แทนการต่อสตริงหรือข้อความแบบทั่วไปในคลาส String โดยจะไปต่อตรงส่วนท้ายสุดของค่าใน object โดยไม่ต้องเปลี่ยนการจองพื้นที่หน่วยความจำ
    ตัวอย่างที่ 1

    StringBuilder sb = new StringBuilder("Hello World!");
    sb.Append(" Nice to meet you!!");
    Console.WriteLine(sb);

    คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นว่าเดิมค่าของตัวแปรมีค่า “Hello World!” แต่เมื่อมีการเรียกใช้เทธอด Append() จะทำการต่อข้อความจากเดิมจนกลายเป็น “Hello World! Nice to meet you!!

    ตัวอย่างที่ 2

    StringBuilder sb = new StringBuilder("Hello World");
    sb.Append(" !?!?",2,2);
    Console.WriteLine(sb);

    คำอธิบาย : จากตัวอย่างข้างต้น จะเห็นว่าเดิมค่าของตัวแปรมีค่า “Hello World” แต่เมื่อมีการเรียกใช้เมธอด Append() แบบมีการ substring ร่วมด้วย โดยจะเริ่มทำการตัดตัวอักษรจากข้อความ “ !?!?” จากตำแหน่งลำดับ index ที่ 2 ไป 2 ตัวอักษร ซึ่งนั่นก็คือ “?!” หลังจากนั้นก็จะเชื่อมต่อด้วยข้อความเดิมจนกลายเป็น “Hello World!?!

    AppendLine() เป็นเมธอดที่ใช้ในการแทรกบรรทัดใหม่เข้าไปให้กับตัวแปร
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello, I am Kate.");
    sb.AppendLine();
    sb.Append("Nice to meet you.");
    Console.WriteLine(sb);
    
    //หรือ
    StringBuilder sb = new StringBuilder("Hello, I am Kate.");
    sb.AppendLine("Nice to meet you.");
    Console.WriteLine(sb);
    
    

    คำอธิบาย : จากตัวอย่างข้างต้น จะเป็นการเพิ่มการขึ้นบรรทัดใหม่ให้กับข้อความของตัวแปร จากค่า “Hello, I am Kate.” ด้วยการขึ้นบรรทัดใหม่ ตามด้วยข้อความ “Nice to meet you.” โดยแสดงทั้งตัวอย่างที่มีการเพิ่มบรรทัดโดยใช้เมธอด AppendLine() ก่อน แล้วจึงเชื่อมข้อความที่เหลือด้วยเมธอด Append() หรืออาจรวบวิธีการขึ้นบรรทัดใหม่ต่อด้วยข้อความด้วยการใช้เมธอด Appendline() แบบมีการส่งค่าพารามิเตอร์ของข้อความที่ต้องการเชื่อมต่อหลังจากขึ้นบรรทัดใหม่ได้เลยดังตัวอย่าง

    AppendFormat() เป็นเมธอดที่ใช้ในการเชื่อมต่อข้อความที่ท้ายสุดของค่าเดิม แบบที่มีการจัดรูปแบบให้กับข้อความที่ต้องการนำมาเชื่อมต่อ โดยรูปแบบที่ใช้ในการกำหนดเป็นรูปแบบมาตรฐานที่ใช้ในการกำหนดรูปแบบของข้อมูลที่เป็นตัวเลข วันที่ และเวลา เป็นต้น
    ตัวอย่าง

    int MyInt = 25;
    StringBuilder sb = new StringBuilder("Your total is ");
    sb.AppendFormat("{0:C} ", MyInt);
    Console.WriteLine(sb);
    

    คำอธิบาย จากตัวอย่าง เป็นการจัดรูปแบบของข้อมูลที่จะนำมาทำการเชื่อมต่อกับตัวแปรเดิม โดยผลลัพธ์ที่ได้ คือ Your total is $25.00 นั่นเอง

    Insert() เป็นเมธอดที่ใช้ในการเพิ่มค่าข้อความแทรกไปยังตัวแปร โดยมีการกำหนดตำแหน่งเริ่มต้นที่ต้องการให้แทรกเพิ่มในค่าข้อมูลดังกล่าวเข้าไปในตัวแปร StringBuilder นั้นด้วย
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello, I am Kate.");
    sb.Insert(5," Mr.Kim");
    Console.WriteLine(MyStringBuilder);

    คำอธิบาย : จากตัวอย่างข้างต้น จะเป็นการแทรกข้อความ “ Mr.Kim” ลงในตัวแปร sb ที่มีค่าเดิมเป็น
    Hello, I am Kate.” จนได้ผลลัพธ์ คือ “Hello Mr.Kim, I am Kate.”

    2.การล้างค่าหรือเอาค่าข้อมูลบางส่วนออก  เป็นการล้างค่าข้อมูลของตัวแปร หรือตัดข้อมูลของตัวแปรบางส่วนออกไป โดยเมธอดที่ใช้งานกันบ่อย มีดังนี้

    Clear() เป็นเมธอดที่ใช้ในการล้างค่าตัวอักษรในข้อความของตัวแปร String builder ดังกล่าว และกำหนดค่าให้กับความยาวตัวอักษรหรือ properties ที่มีชื่อว่า “Length เป็น 0
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!");
    sb.Clear();
    Console.WriteLine(sb);

    คำอธิบาย : จากตัวอย่างข้างต้นจะเป็นการล้างค่าข้อมูลให้กับตัวแปร sb จากเดิมที่มีค่าตั้งต้นเป็น “Hello World!” จะเหลือเป็นค่าว่าง หรือ String.Empty และหากต้องการให้แสดงผลจะไม่แสดงข้อความใดๆขึ้นมา แต่ยังคงจองพื้นที่หน่วยความจำไว้ให้กับตัวแปรนี้แม้จะไม่มีค่าข้อมูลใดๆก็ตาม และจะมีค่า Length เป็น 0 นั่นเอง

    Remove() เป็นเมธอดที่ใช้ในการตัดค่าของข้อความในตัวแปรออก โดยมีการกำหนดตำแหน่งเริ่มต้นและความยาวของตัวอักษรในข้อความที่ต้องการตัด โดยนับตำแหน่งจากตัวอักษรตัวแรกเป็นค่าตำแหน่งลำดับที่ 0(ซึ่งเป็นการอ้างอิงตามหลักการนับตำแหน่งของ index โดยเริ่มถือว่าตัวอักษรตัวแรกเป็น index ลำดับที่ 0 นั่นเอง)
    ตัวอย่าง

    StringBuilder sb = new StringBuilder("Hello World!");
    sb.Remove(5,7);
    Console.WriteLine(sb);
    

    คำอธิบาย : จากตัวอย่างข้างต้น เดิมตัวแปรมีค่าข้อความเป็น “Hello World!” แต่เมื่อใช้เมธอด Remove() ในการตัดค่าข้อความตั้งแต่ตำแหน่งของลำดับ index ที่ 5 (หรือตำแหน่งที่ 4 เมื่อเริ่มต้นนับ 1 จากตัวอักษรแรกของข้อความ) ไปจำนวน 7 ตัวอักษร โดยผลลัพธ์ที่ได้ เป็นดังนี้ “Hello

    • การตัดค่าบางส่วนออกโดยกำหนดค่า properties ที่ชื่อว่า Length ให้กับตัวแปร

    ตัวอย่าง

    StringBuilder sb1= new StringBuilder("Hello World!");
    sb1.Length--;
    //หรือ
    sb1.Length = sb1.Length-1;
    //หรือ
    sb1.Remove(sb1.Length-1,1);
    //เป็นการตัดค่าตัวอักษรตัวสุดท้ายออกจากข้อความ จะได้ผลลัพธ์ คือ Hello World 
    
    StringBuilder sb2= new StringBuilder("Hello World!"); 
    sb2.Length=0; 
    //เป็นการล้างค่าข้อมูลในตัวแปร sb2 
    ซึ่งจะมีลักษณะการทำงานเช่นเดียวกับการใช้เมธอด Clear() ที่กล่าวไว้แล้วข้างต้นนั่นเอง

    คำอธิบาย : จากตัวอย่างข้างต้น ในส่วนของ sb1 นั้นจะเป็นการตัดค่าตัวอักษรตัวสุดท้ายออกจากข้อความ “Hello World!” ซึ่งก็คือตัวอักษร “!” โดยจะได้ผลลัพธ์ เป็น “Hello World” และสำหรับกรณีของ sb2 จะเป็นการล้างค่าข้อมูลในตัวแปร sb2 ซึ่งจะมีลักษณะการทำงานเช่นเดียวกับการใช้เมธอด Clear() ที่กล่าวไว้แล้วข้างต้นนั่นเอง

    3. การเปลี่ยนแปลงค่าของตัวแปร  ดังนี้

    • Replace() เป็นเมธอดที่ใช้ในการแทนที่ข้อความ โดยมีลักษณะการทำงานเช่นเดียวกับเมธอด Replace() ที่มีในคลาส String ที่จะต้องมีการกำหนดค่าใหม่ที่ต้องการให้แทนที่ และค่าที่ต้องการแทนที่นั่นเอง
    ตัวอย่าง

    StringBuilder sb= new StringBuilder("Hello World!");
    sb.Replace('World', 'Kate');
    Console.WriteLine(sb);
    

    คำอธิบาย จากตัวอย่างข้างต้น จะเป็นการแทนที่ค่าของข้อความเดิมคือ “World” ในคำว่า “Hello World!” ด้วยค่า “Kate” ซึ่งจะได้ผลลัพธ์ คือ “Hello Kate!

    4. การแปลงค่าจากตัวแปรชนิด StringBuilder เป็น String  ดังนี้

    ToString() เป็นเมธอดที่ใช้ในการแปลงค่าตัวแปรแบบ StringBuilder มาเป็นค่าของตัวแปรแบบคลาส String เพื่อนำไปแสดงผลหรือนำมาประยุกต์ใช้งานกับเมธอดที่มีในคลาส String ต่อไป
    ตัวอย่าง

    StringBuilder sb= new StringBuilder("Hello World!");
    Console.WriteLine(sb.ToString());
    //การนำไปใช้กับเมธอดหรือฟังก์ชั่นที่มีในคลาส String
    int a = sb.ToString().IndexOf("a");
    //เป็นการค้นหาตำแหน่งของอักษร a ในตัวแปร sb ที่ถูกแปลงเป็น String 
    ซึ่งผลลัพธ์ที่ได้จะเท่ากับ -1 เนื่องจากไม่มีตัวอักษร a ในข้อความดังกล่าว

    คำอธิบาย : จากตัวอย่างข้างต้นเป็นการแปลงค่าของตัวแปรในคลาส StringBuilder ให้เป็นคลาส String ซึ่งหลังจากที่ได้ทำการแปลงค่าแล้วนั้น จะสามารถใช้งานเมธอหรือฟังก์ชั่นที่มีในคลาส String ได้ เช่น IndexOf หรือ StartsWith  เป็นต้น

    5. การอ้างอิงและเข้าถึงตัวอักษรที่อยู่ในข้อความ  ดังนี้

    ตัวอย่าง

    StringBuilder sb= new StringBuilder("Hello World!");
    Console.WriteLine(sb[6]);
    //ผลลัพธ์ที่ได้คือ "W"
    sb[2] = 'k';
    //ผลลัพธ์ที่ได้คือ "Heklo World!"

    คำอธิบาย : จากตัวอย่างข้างต้นเป็นการเข้าถึงตำแหน่งของค่าตัวแปรชนิด StringBuilder ผ่านการอ้างลำดับ index ของอักขระในตัวแปรนั้น และตัวอย่างถัดมาจะเป็นการเปลี่ยนแปลงค่าโดยการเข้าถึงอักขระในลำดับ index ที่กำหนด โดยให้ค่าใหม่แทนที่

    6. การค้นหา  ในการค้นข้อมูลของตัวแปรแบบ StringBuilder นั้น โดยทั่วไปแล้วจะไม่มีเมธอดที่ใช้ในการค้นหาข้อมูลโดยตรง ดังเช่นที่มีในคลาส String เช่น IndexOf หรือ StartsWith ดังนั้นเราสามารถประยุกต์วิธีการค้นหาตามลักษณะของข้อมูลและการจัดการได้ ดังนี้

    • การค้นหาค่าที่ได้กลับมาในรูปแบบ Stringจากการเรียกใช้เมธอด ToString() โดยใช้เมธอดที่มีเช่นเดียวกับในคลาส String ซึ่งเป็นวิธีที่ง่าย และสามารถเปลี่ยนแปลงข้อมูลของตัวแปรได้โดยไม่สิ้นเปลืองทรัพยากร
    • แปลงค่าของตัวแปรแบบ StringBuilder ให้เป็นแบบ String และใช้เมธอดในการค้นหาที่มีในคลาส String ซึ่งวิธีการนี้เหมาะกับการทำงานที่ไม่มีการเปลี่ยนแปลงภายหลังจากการแปลงค่ามากนัก เพื่อลดการสิ้นเปลืองทรัพยากรในการแปลงค่าภายหลัง
    • ค้นหาข้อมูลให้เสร็จก่อนทำการนำมาแปลงค่าเป็นแบบ StringBuilder โดยวิธีนี้จะต้องไม่สนใจค่าลำดับของตัวอักษรที่ได้จากการค้นหานั้นๆ
    • ค้นหาโดยใช้ Properties ทีชื่อว่า Chars โดยวิธีนี้เหมาะกับข้อมูลที่มีอักษรจำนวนไม่มาก และเงื่อนไขในการค้นหาไม่ซับซ้อน

    เพิ่มเติม
    • หลักการเบื้องต้นในการพิจารณาการใช้งานกับการจัดการข้อมูลของตัวแปรข้อความ(String)ที่ใช้เมธอดการทำงานในแบบเดียวกัน หากเป็นการใช้งานที่มีการเชื่อมต่อข้อความในตัวแปรเดิมที่มากกว่า 4 ครั้ง ผู้พัฒนาควรหันมาใช้ตัวแปรแบบคลาส StringBuilder เนื่องจากจะทำให้ประหยัดทรัพยากรหน่วยความจำมากกว่า แต่หากจำนวนครั้งในการเชื่อมต่อน้อยกว่านั้นควรใช้คลาสของ String ตามเดิมเพื่อความสะดวกและรวดเร็ว เนื่องจากมีเมธอดรองรับการจัดการข้อความที่หลากหลายและไม่ถือเป็นการสิ้นเปลืองพื้นที่หน่วยความจำมากนัก

    • ในการเรียกใช้งานเมธอดต่างๆ สามารถนำมาเขียนรวมกันใน statement เดียวกันได้ ดังนี้

    ตัวอย่างที่ 1

          StringBuilder sb = new StringBuilder();
          sb.Append("This is the beginning of a sentence, ");
          sb.Replace("the beginning of ", "");
          sb.Insert(sb.ToString().IndexOf("a ") + 2, "complete ");
          sb.Replace(",", ".");
          Console.WriteLine(sb.ToString());
    

    คำอธิบาย : จากตัวอย่างข้างต้นเป็นการเชื่อมต่อข้อความเริ่มต้น คือ “This is the beginning of a sentence, ” และแทนที่คำว่า  “the beginning of” ด้วย “”(ค่าว่าง) และแทรกข้อความคำว่า “complete ” เข้าไปในตำแหน่งซึ่งหามาได้จากการเรียกใช้เมธอด ToString() และตามด้วยเมธอด IndexOf ของตำแหน่งข้อความ “a ” ถัดไปอีก 2 ตำแหน่งด้วยค่า “complete ” และแทนที่เครื่องหมาย “,” ด้วย “.”

    สามารถเขียนรวมเป็น statement เดียวกันได้ดังนี้

    ตัวอย่างที่ 2

          StringBuilder sb = new StringBuilder("This is the beginning of a sentence, ");
          sb.Replace("the beginning of ", "").Insert(sb.ToString().IndexOf("a ") + 2, 
                                                     "complete ").Replace(",", ".");
          Console.WriteLine(sb.ToString());

    ซึ่งจะให้ผลลัพธ์เช่นเดียวกับตัวอย่างที่ 1

              จากบทความข้างต้นที่กล่าวมาทั้งหมด จะเห็นได้ว่าประสิทธิภาพการจัดการข้อมูลจะดีเพียงใดนั้น ย่อมขึ้นอยู่กับการพิจารณาและเลือกใช้วิธีการจัดการข้อมูลที่ถูกต้องและเหมาะสมกับปัจจัยต่างๆ เช่น จำนวนของข้อมูล จำนวนครั้งในการเปลี่ยนแปลงข้อมูล และจำนวนทรัพยากรที่ใช้ในการพัฒนา เป็นต้น ซึ่งข้อมูลในบทความนี้เป็นเพียงข้อมูลในเบื้องต้นเกี่ยวกับ StringBuilder เท่านั้น ยังคงมีข้อมูลรอให้พวกเรานักพัฒนาทั้งหลายได้เข้าไปทำการศึกษาและเรียนรู้กันอีกมากมาย หากผู้ใดมีความสนใจสามารถค้นหาเพิ่มเติมและมาร่วมกันแบ่งปันประสบการณ์ร่วมกันได้ และผู้เขียนหวังเป็นอย่างยิ่งว่าข้อมูลดังกล่าวจะช่วยให้การตัดสินใจเลือกใช้วิธีการจัดการข้อมูลเป็นไปได้ง่ายขึ้น และโปรแกรมที่พัฒนาทำงานได้มีประสิทธิภาพรวดเร็วขึ้นไม่มากก็น้อย หากผิดพลาดประการใด ผู้เขียนขออภัยมา ณ ที่นี้ด้วยค่ะ

    แหล่งข้อมูลอ้างอิง :
    http://www.dotnetperls.com/stringbuilder
    https://msdn.microsoft.com/en-us/library/2839d5h5(v=vs.110).aspx
    http://www.tutorialsteacher.com/csharp/csharp-stringbuilder
    https://blog.udemy.com/stringbuilder-c-sharp/