Saturday, April 7, 2012

Provider Model ใน ASP.Net 2.0 ตอนที่ 2

ในตอนที่แล้วได้กล่าวถึง Provider Model และประโยชน์ของการใช้ Provider Model แล้ว ซึ่งจุดเด่นจุดหนึ่งของการใช้ Provider Model ก็คือ เราสามารถเขียน Provider ขึ้นมาเพื่อใช้งานกับระบบของเราได้ในกรณีที่ Provider ที่มีอยู่ไม่ตรงกับความต้องการของเรา ในครั้งนี้เราจะมาลองเขียน Provider กัน
ตัวอย่างการเขียน Provider ขึ้นใช้เอง
ใน ASP.NET 2.0 มีระบบ SiteMap ผนวกมาอยู่แล้วพร้อมกับ Provider หนึ่งตัว คือ XmlSiteMapProvider เมื่อดูจากชื่อเราอาจจะเดาได้ว่า Provider นี้ใช้ไฟล์ XML ในการเก็บข้อมูล ซึ่งแก้ไขได้ลำบาก หากเราเปลี่ยนไปเก็บไว้ในฐานข้อมูล จะเขียนโปรแกรมสำหรับเพิ่ม-ลบ-แก้ไขข้อมูลได้สะดวกกว่า ดังนั้นเราจึงต้องเขียน Provider เพื่อทำงานกับฐานข้อมูลไมโครซอฟท์แอคเซส โดยให้ชื่อว่า AccessSitemapProvider ตามธรรมเนียมการตั้งชื่อของ provider คือ [data source][feature]Provider
ก่อนอื่น สมมติว่าเราใช้ระบบ SiteMap อยู่แล้ว โดยในไฟล์ Web.sitemap มีข้อมูลดังนี้
<?xml version=”1.0″ encoding=”utf-8″ ?>

<siteMap xmlns=”http://schemas.microsoft.com/AspNet/SiteMap-File-1.0″ >
   <siteMapNode url=”~/default.aspx” title=”Home” description=”Home page for this site”>
     <siteMapNode url=”~/about.aspx” title=”About” description=”About me” />
     <siteMapNode url=”~/links.aspx” title=”Links” description=”Links to the world” />
   </siteMapNode>
</siteMap>
ตัวอย่างไฟล์ Web.sitemap
คลาส XmlSiteMapProvider ที่เป็น Provider หลักของ SiteMap เป็นคลาสที่สืบทอดมาจาก StaticSiteMapProvider ที่เป็นคลาสลูกของ SiteMapProvider อีกที เราต้องการเขียน AccessSiteMapProvider ดังนั้นเราก็จะต้องสร้างคลาสที่สืบทอดมาจากคลาส StaticSiteMapProvider ซึ่งมีเมธอดที่เราจะต้องทำการ override ดังนี้
  • BuildSiteMap() ใช้ในการสร้าง SiteMap จากแหล่งข้อมูลและเก็บไว้ในหน่วยความจำ และคืนค่า Root Node ออกมา
  • GetRootNodeCore() เป็นเมธอดที่จะคืนค่า Root Node มาให้เช่นกัน
จากนั้นก็ทำการออกแบบฐานข้อมูลในโปรแกรมไมโครซอฟท์แอคเซส
หลังจากนั้น ทำการเพิ่มข้อมูลของ SiteMap ลงในฐานข้อมูล โดยใส่ข้อมูลเหมือนกับในไฟล์ Web.sitemap
เมื่อทุกอย่างพร้อมก็เริ่มทำการเขียน Provider ของเราขึ้นมาได้เลย ก่อนอื่นให้ทำการสร้างโปรเจกต์ขึ้นมาใหม่โดยเลือกให้เป็น Class Library และตั้งชื่อว่า SiteMapProvider หลังจากนั้นให้ทำการเพิ่ม Reference โดยจะต้องเพิ่ม System.Configuration และ System.Web ให้กับโปรเจกต์ด้วย
using System;

using System.Text;
using System.Collections;
using System.Configuration.Provider;
using System.Web;
using System.Data;
using System.Configuration;
using System.Data.OleDb;
using System.Web.Configuration;
namespace ThaiSharp

{
  public class AccessSiteMapProvider : StaticSiteMapProvider
  {
    private string connectionstringname;

    private SiteMapNode root;
    public override void Initialize(string name, System.Collections.Specialized.NameValueCollection attributes) {
    // initialize Provider
    base.Initialize(name, attributes);
    // Cannot find any attributes.
    if (attributes == null)
      throw new ConfigurationErrorsException(“Missing ConnectionStringName attribute.”);
    // get Connection string information
    connectionstringname = attributes["ConnectionStringName"];
    if (connectionstringname == null){
      throw new ConfigurationErrorsException(“Missing ConnectionStringName attribute.”);
  }
}
  protected override void Clear() { root = null; base.Clear();}
  // helper method for populate Sitemap Node from DataReader.
  protected SiteMapNode PopulateSiteMapFromReader(IDataReader reader, int id, int url, int title, int desc, int roles, int parent) {
     … // populate SitemapNode from reader
  }
  public override SiteMapNode BuildSiteMap() {
    if (root != null) return root;
      lock (this) // lock this object to prevent concurrency.
    {
      // Retrieve Sitemap data from database.. .
      return root;
    }
  }
  protected override SiteMapNode GetRootNodeCore() {
    BuildSiteMap();
    return root;
  }
}
}
โค้ดของคลาส AccessSiteMapProvider บางส่วน
จากโค้ดดังกล่าว มีจุดที่น่าสนใจคือเมธอด Initialize ซึ่งจะทำงานเมื่อ AccessSiteMapProvider เริ่มทำงานครั้งแรก ซึ่งจะเป็นตัวกำหนด connection string ให้กับ provider ผ่านการเรียก property attributes ซึ่ง property นี้เป็น property ของคลาส ProviderBase ดังนั้น เราจึงเรียกใช้ property นี้ได้จากทุก Provider โดย property นี้จะเป็นตัวที่ช่วยกำหนดค่าหรือ config ต่าง ๆให้กับ provider อย่างง่ายดาย จากโค้ดด้านบนจะเป็นการดึงข้อมูลจาก attribute ที่ชื่อ ConnectionString ออกมาใช้
ทดสอบการใช้งาน AccessSiteMapProvider
เมื่อเราสร้าง AccessSiteMapProvider เสร็จเรียบร้อยแล้วก็ได้เวลาทดสอบ Provider ที่เราเพิ่งสร้างโดยการนำไฟล์ dll ที่เราได้มาจากขั้นที่แล้วไปใส่ไว้ในไดเรกทอรีที่ชื่อ bin ของเว็บไซต์ของเรา แล้วทำการกำหนดค่าใน Web.config เพื่อให้ตัวโปรแกรม ASP.NET เลือกใช้ provider ของเราสำหรับเว็บไซต์ที่สร้างขึ้นนี้
<configuration>

  <connectionStrings>
    <add name=”SitemapDatabase” providerName=”System.Data.OleDb” connectionString=”… “/>
  </connectionStrings>
  <system.web>
    <siteMap defaultProvider=”AccessProvider“>
       <providers>
          <add name=”AccessProvider” type=”ThaiSharp.AccessSiteMapProvider, ThaiSharp”
ConnectionStringName=”SitemapDatabase”/>
       </providers>

    </siteMap>
  </system.web>
</configuration>
ตัวอย่างการตั้งค่าใน web.config
จากนั้น เราก็นำฐานข้อมูลที่สร้างไว้แล้วไปใส่ในไดเร็กทอรี่ /App_data แล้วกำหนดค่าในส่วนของConnectionString ให้ถูกต้อง แล้วทำการทดสอบด้วยการเรียกเว็บไซต์ของเราขึ้นมา จะเห็นว่าผลลัพธ์ที่ได้ไม่แตกต่างจากการใช้ XmlSiteMapProvider โดยที่เราไม่ต้องแก้ไขโค้ดในส่วนที่ใช้ในการแสดงผล SiteMap เลย ซึ่งเป็นจุดเด่นในระบบที่ใช้ Provider Model ที่ไม่ต้องสนใจว่า Provider ด้านล่างทำงานอย่างไร เก็บข้อมูลไว้ที่ไหน แค่สามารถทำงานได้ถูกต้องตามที่ต้องการก็เพียงพอแล้ว
เพิ่มเติมความสามารถให้ AccessSiteMapProvider
การใช้ Access database มาใช้เก็บ SiteMap มีข้อดีตรงที่เราสามารถเขียนโปรแกรมสำหรับจัดการได้สะดวกกว่าการใช้ XML แต่ใน SiteMap Provider ไม่ได้รองรับส่วนนี้ ดังนั้นเราจำเป็นจะต้องเพิ่มส่วนของการเพิ่ม node ลงในฐานข้อมูลขึ้นมาเอง โดยมีวิธีการคร่าว ๆ ดังนี้
ขั้นแรก เขียนเมธอด AddNewNodetoDb ในคลาส สำหรับเพิ่ม node ใหม่ลงในฐานข้อมูล
public void AddNewNodetoDb(string url, string title, string description, string roles, int parentid) {
  if (root == null) BuildSiteMap();
  int id = 0; // id for new node.
  // Retreive connection information from ConnectionString element in web.config
  string connst = “Provider=Microsoft.Jet.OLEDB.4.0;Data Source=” +  HttpContext.Current.Server.MapPath( WebConfigurationManager.ConnectionStrings[ connectionstringname].ConnectionString);
  using (OleDbConnection conn = new OleDbConnection (connst))
  {

    // Add new node into database
    …
    // Add new node into SiteMapTree
    SiteMapNode parent = this.FindSiteMapNodeFromKey(parentid.ToString());
    SiteMapNode node = new SiteMapNode(this, id.ToString(), url, title, description);
    if (!String.IsNullOrEmpty(roles)){
      string[] roleslist = roles.Split(new char[] { ‘,’, ‘;’ });
      node.Roles = roleslist;
    }
    if (node.Roles == null) node.Roles = new string[] { “*” };
    AddNode(node, parent);
  }
}
เมธอด AddNewNodeToDb ที่ใช้ในการเพิ่ม node ลงในฐานข้อมูล
โค้ดดังกล่าวจะทำการตรวจสอบก่อนว่ามี url ที่ระบุลงในฐานข้อมูลหรือยัง ถ้ายังไม่มีจึงจะทำการเพิ่มข้อมูลลงในฐานข้อมูล แล้วจึงทำการเพิ่ม Node นี้ลงใน ระบบของ SiteMap ด่วยคำสั่ง AddNode ถ้าไม่ทำขั้นตอนนี้ ข้อมูลที่เราเพิ่มเข้าไปจะเพิ่มเฉพาะใน ฐานข้อมูลเท่านั้น แต่จะไม่เข้าไปใน SiteMap จนกว่าตัวโปรแกรม ASP.NET จะทำการสร้าง SiteMap ใหม่ หลังจากนั้นก็ทำการสร้างเว็บฟอร์มเพื่อสร้าง Node ใหม่ โดยมีโค้ดดังนี้
<%@ Page Language=”C#” %>
<%@ Import Namespace=”ThaiSharp.SiteMapProvider” %>
<script runat=”server”>
void Page_Load(object sender, EventArgs e){
  if (SiteMap.Provider.Name != “AccessProvider”) {
     btnAdd.Text = “You’re not using AccessSiteMapProvider. Feature disabled”;
     btnAdd.Enabled = false;
}
  else if (!Page.IsPostBack) {
      AccessSiteMapProvider provider = (AccessSiteMapProvider)SiteMap.Provider;
      SiteMapNodeCollection coll = provider.GetChildNodes(SiteMap.RootNode);
      ddlParent.Items.Add(new ListItem(SiteMap.RootNode.Title, SiteMap.RootNode.Key));
      foreach (SiteMapNode node in coll){
        ddlParent.Items.Add(new ListItem(node.Title, node.Key));
      }
   }
}
protected void btnAdd_Click(object sender, EventArgs e) {
     AccessSiteMapProvider provider = (AccessSiteMapProvider)SiteMap.Provider;
     try {
        provider.AddNewNodetoDb(txtUrl.Text, txtTitle.Text, txtDesc.Text, txtRoles.Text, Convert.ToInt32(ddlParent.SelectedValue));
        lblStatus.Text = “Node added.”;
        Response.Redirect(Request.RawUrl);
     }
     catch (Exception ex) {
        lblStatus.Text = ex.Message;
     }
}
</script>
โค้ดของหน้าเว็บฟอร์มที่ใช้เพิ่ม Node ลงในฐานข้อมูล (ไม่แสดงส่วนของโค้ด HTML)
ข้อควรระวังของการเพิ่มความสามารถต่าง ๆ ให้กับ Provider นั้นคือ ก่อนจะเรียกใช้งานเราต้องตรวจสอบดูว่า Provider ที่เรากำลังใช้อยู่นั้นเป็น Provider ที่เราเพิ่มเมธอดเข้าไปจริง ๆ เช่นในกรณีเราก็จะต้องตรวจสอบว่า SiteMapProvider ที่เรากำลังใช้อยู่เป็น AccessSiteMapProvider จริง ๆ แล้วจึงทำการแปลงชนิดข้อมูลของ Provider ให้เป็นอ็อปเจกต์ของคลาส AccessSitemapProvider เพราะปกติแล้ว ASP.NET จะไม่มอง Provider ของเราเป็นอ็อปเจกต์ของ AccessSitemapProvider แต่จะมองว่าเป็นอ็อปเจกต์ของคลาส SiteMapProvider เราจึงไม่สามารถเรียกใช้งานเมธอด AddNewNode ของเราได้ถ้าไม่ได้มีการแปลงชนิดข้อมูลก่อน
สรุป
จากตัวอย่างข้างต้นนี้จะเห็นได้ว่าการเขียน Provider ขึ้นใช้เองนั้นไม่ใช่เรื่องยุ่งยากหรือซับซ้อนแต่อย่างใด ก่อนที่เราจะเขียนเราก็ทำการศึกษาก่อนว่า Provider ที่จะเขียนนั้นควรจะสืบทอดมาจากคลาสใด เพราะ Provider ของแต่ละระบบก็จะมีคลาสต้นแบบแตกต่างกัน และคลาสต้นแบบนั้นก็จะมีโครงสร้างของ Provider ที่เราจะต้องทำการเขียนโค้ดที่แตกต่างกันออกไป อย่างไรก็ตาม ตอนนี้เรารู้จักเพียงวิธีการสร้าง Provider ขึ้นมาใช้งานกับระบบที่มีอยู่แล้ว แต่ถ้าเกิดเราต้องการจะสร้าง Provider ใหม่ที่รองรับการทำงานของเรา เช่น Provider สำหรับเว็บบอร์ดหรือฟอรั่ม หรือ Provider สำหรับ Blog Engine เราจะสร้างมันขึ้นมาได้อย่างไร ในคราวหน้าเราจะลองมาดูกันว่าถ้าเราจะเขียน Provider เพื่อใช้งานกับระบบของเราเอง ทาง ASP.NET เตรียมอะไรไว้ให้เราบ้าง แล้วเราจะสร้าง Custom Provider-Based Service ขึ้นมาได้อย่างไร


ref : http://onedd.net/pages/provider-model-%E0%B9%83%E0%B8%99-asp-net-2-0-%E0%B8%95%E0%B8%AD%E0%B8%99%E0%B8%97%E0%B8%B5%E0%B9%88-2/

No comments:

Post a Comment