2013年12月22日 星期日

What's Windows Azure Part 1 儲存方式 - Blob、Tables、Queues

Window Azure這東西,其實有非常多且新的專有名詞,對於很多傳統開發者而言,雲端是一個個如同「神龍見首不見尾一般」的一團迷霧。尤其是許多的概念技術,在傳統的開發模式中,它抽離了許多開發者必需要去注意、維護、升級、資料轉移等繁鎖的事項,「雲端系統」把這些工作,全都交由專職於這類技術產業的公司去負責。筆者花了時間從微軟官方和網路上各高手的資訊分享上,匯整了資訊,依內容切割介紹,本篇就來介紹一些需端的東西,基本的專有名詞,和三種儲存方式。

Windows Azure 平台是一種「平台即是服務 Platform as a Servive」(PaaS) 產品,它具有針對資料中心架構的自我管理(autonomous)機能,可以自動監控劃分在資料中心數個不同的分區(微軟將這些分區稱為Fault Domain)的所有伺服器與儲存資源,自動更新修補程式,自動執行虛擬機器部署與鏡像備份(Snapshot Backup)等能力。這提供組織一種平台來執行 Windows 應用程式並將資料儲存至雲端。這些應用程式可以是組織目前已經過轉換後的既有應用程式,或者是為了要在 Windows Azure 上執行而專門設計的全新應用程式。開發者可以使用熟悉的工具,利用 Windows Azure 雲端服務快速部署及管理功能強大的應用程式和服務。直接上傳應用程式,Windows Azure 就會部署詳細資料 - 從佈建和負載平衡,到持續可用性的健康狀況監視。開發者只要專心在應用程式,不需擔心基礎結構,Windows Azure不但是開發給外部的雲端應用程式使用的,它也作為微軟許多雲端服務的基礎平台,像Windows Azure SQL Database或是Dynamic CRM Online這類的線上服務。

Windows Azure 提供各種的服務:Windows Azure、SQL Azure 以及 Windows Azure 平台 AppFabric。這些的組成等於是作業系統、資料庫以及「通訊與安全」服務。

Windows Azure 提供三個核心元件,分別是運算 (Compute)、儲存庫 (Storage) 以及結構 (Fabric)。「運算」實際上就是 Windows 作業系統,這是一種執行個體(instance)。這些執行個體包含應用程式的複本。執行個體有兩種,一種是 Web 角色,另一種是 Worker 角色。Web 角色會使用 IIS 來接受以及處理 HTTP 要求,Worker 角色執行個體與 Web 角色執行個體非常相似。差別在於 Worker 角色並未由 IIS 預先設定為在每個執行個體中執行。Web 角色和 Worker 角色可以透過 WCF 的技術,或者 Windows Azure Storage 查詢,就可以彼此溝通。但Web 角色和 Worker 角色都需要儲存資料。Windows Azure 提供三種儲存選項,分別是 Blob、表格(Tables)以及佇列(Queues)。Blob 使用最簡便,它們可以非常大,最多可高達 1 TB,而且可以分割成數個區塊。要想使用 Blob,另一種方法就是透過 Windows Azure Drives – Web 角色執行個體或 Worker 角色執行個體掛載。Windows Azure Drives 相關的儲存庫就是 Blob。磁碟機掛載之後,這個執行個體可以讀取或寫入資料,這樣便可以永久儲存到 Blob 中。

Blob 只適用於某些類型的資料,大部分時它們的結構很鬆散。為了讓應用程式可以用更精細的方式來處理資料,Windows Azure 儲存庫提供表格儲存。但這些稱為「表格」,並不是關聯式表格,它們包含的實際上是儲存在一組附帶各種屬性的個體中。表格沒有定義的結構描述;反而,屬性可以有不同的類型,例如 int、string、 Bool 或 DateTime。而且除使用 SQL 外,應用程式可以使用 ADO.NET 資料服務或 LINQ 來存取資料。單張表格可以十分巨大,裡面有百億個個體在保存數以 TB 的資料,並且在需要時,Windows Azure 儲存庫可以將它切割至多個伺服器,用來提高效能。

BLOB依照性質分為兩種:
  • Block BLOB(區塊型BLOB儲存體),這類的儲存以4MB為一個區塊單位,單一檔案最大可以儲存200GB,且區塊不會連續儲存,可能會打散到不同的儲存伺服器中存放,當應用程式要求時,會依照檔案的Key以及區塊由儲存區提取資料。另外,區塊在儲存時會經過一道認可程式,以讓應用程式決定是否要重新傳送。
  • Page BLOB(分頁型BLOB儲存體),它會在儲存區中劃分一個連續的區域供應用程式存放資料,它本身可以視為一個大型的VHD(虛擬機器磁碟),在Page BLOB的資料寫入會直接認可。而基於Page BLOB的特性,微軟特別在Page BLOB上提供了一組將Page BLOB虛擬成磁碟的功能,稱為Windows Azure Drive(研發代號為XDrive),它能夠支援NTFS API,也就是說應用程式可以利用現有的檔案管理API(包含System.IO的型別)來存取Windows Azure Drive中的資料夾與檔案資料,並且這些資料會保存在Windows Azure資料中心內。
Blob服務由Blob本身以及其收納容器(Container)構成,容器可視為一般本機上的資料夾。而容器和BLOB都支援額外的Metadata設定,這些Metadata會附掛在HTTP Header中傳輸給用戶端,每一個Metadata的大小限制為8KB。Blob也支援權限管理的功能,透過Shared Access Signature可設定Blob或Container的存取權限與有效期限等。

Windows Azure 儲存庫中的第三個選項是佇列,佇列的用途主要是提供一種方法給 Web 角色執行個體來與 Worker 角色執行個體進行溝通。例如,使用者可以提交一項要求,透過 Windows Azure Web 角色運行的網頁來執行某些需要大量運算的工作。收到這項要求的 Web 角色執行個體可以將一則訊息寫入佇列中,用於描述要完成的工作。然後,等候這個佇列的 Worker 角色執行個體可以讀取訊息並執行指定的工作。任何結果都可以透過另一個佇列傳回或者用另一種方法加以處理。好比是開發一個訊息傳送的功能,透過佇列功能,將每個要傳送的訊息寫入佇列,然後再指定要處理的時間。

Table(表格)是給具結構化資料的應用程式儲存與管理的一種儲存服務,它在Windows Azure儲存區中會以Key-Value鍵值對方式儲存,並且由Partition來切割在Windows Azure儲存區的儲存位置,它實際的資料是XML,透過REST API呼叫時,會需要依據SDK上的說明,自行建置XML的要求與解析回應的資料,但若是.NET Framework,在Windows Azure SDK中提供的Microsoft.WindowsAzure.StorageClient命名空間就有提供輔助的API以及型別,將這件事在API中處理掉了。SDK提供的輔助組件所應用的技術是WCF Data Services。

Queue(佇列)是一種先到先服務(First-Come, First-Serve),或稱為FIFO(先入先出)的儲存服務,它可以允許應用程式將訊息儲存到佇列中排隊,然後由負責處理的應用程式(通常是Worker)由佇列提取訊息並處理以後,將訊息由佇列中移除。訊息可以是字串或是最長8KB的二進位資料,佇列經常會作為跨執行個體通訊以及工作切割通知的訊息傳遞之用。

-雲遊山水為知已逍遙一生而忘齡- 電腦神手

2013年12月20日 星期五

C# - Types的判斷方法

這次文章想小小的偷懶一下,所以分享一些小方法技巧,有關於Type的判斷方式

CheckBox cbt = cb as CheckBox;

(cbt != null) cbt.Checked = true;

宣告一個CheckBox並且將被判斷的變數用as轉換,
如果 cb 不是 CheckBox的話,cbt會等於null。

第二個方法是用GetType(),
GetType().Name可以取出Type名稱的字串,
好處是可以用switch…case判斷結果

string ValueString;
switch (Value.GetType().Name)
{
    case "String":
        ValueString = (string) Value;
        break;

    case "Int32":
        ValueString = ((int) Value).ToString();
        break;

    case "Boolean":
        ValueString = Convert.ToString((bool) Value);
        break;

    default:
        throw (new ApplicationException("型別不符"));
        break;

}

第三個方法
private void Control_Click(object sender, EventArgs e)
{
    if (sender is Button)
        this.Text = "Button";
    else if (sender is CheckBox)
        this.Text = "CheckBox";
}


-雲遊山水為知已逍遙一生而忘齡- 電腦神手

2013年12月19日 星期四

從遊戲的視角學C#

我接觸遊戲的開發有幾年的時間,其實不算長,不過倒是學習到很多曾經我較不足的部分,尤其是將Design Pattern運用在開發上。在C#的Library裡,微軟提供了許多的函式庫,對於開發軟體、遊戲、硬體控制、Web網路程式,都是非常好用。本文將要討論從遊戲的角度來看類別的運用。

首先,透過類別來分離出我們的遊戲的不同部分有關的理論。

遊戲中使用Class

一開始的遊戲結構上,沒有為了不同的遊戲部分裡的Events而使用Class。而是用了一個規則化的程式邏輯來寫這遊戲功能。在戰鬥循環動作中,讓Program類別只需打開Main()和去調用MainGame()。主要是初始者知道,開發遊戲裡涵蓋的基礎知識(移動世界各地,買東西..等。),這對於一般剛接觸用C#來開發遊戲的人來說,有許多不了解的地方。然後,我們需要一個類別來處理Battles,讓戰鬥流程不斷迴圈,就如同玩一款遊戲一樣,開始->玩遊戲->結速->再開始。現在需要打開一個全新的項目。把它叫做RpgTutorial。所以,進入“File - >New Project”。然後選擇“Console Application(控制台應用程式)”,並將其命名RpgTutorial。

現在,我們需要添加兩個類別,取名MainGame和Battle。右鍵單擊RpgTutorial中,選擇Add - >class,並將其命名為MainGame,並重複這個過程,叫它是Battle。現在,有三個基底類別的工作。讓Main()能呼叫MainGame類別。

namespace RpgTutorial
{
    class Program
    {
        static void Main(string[] args)
        {
            MainGame maingame = new MainGame();
        }
    }

}

MainGame沒有建構函數的權利,但MainGame建構函數將不會發送給它任何參數。現在MainGame需要一個遊戲循環,並在該循環將在某個時候調用Battle class。所以要製造一個do/ while循環。

namespace RpgTutorial
{
    class MainGame
    {
        Battle battle;
        string answer;

        public MainGame()
        {
            BasicGameLoop();
        }

        void BasicGameLoop()
        {
            do
            {
                battle = new Battle();
                Console.WriteLine("Do you want to play again?");
                answer = Console.ReadLine();
            }

            while (answer == "Y" || answer == "y");
        }
    }

}


從上面的程式結構可以了解遊戲的「基本迴路」怎麼回事。此時的戰鬥不接受任何參數。這所以運行中畫面會很快的改變,因為沒有控制。此外,宣告我Battle class 到第二個步驟,而不是第一個步驟。

所以,現在有三個class來處理程序的不同狀態。一個用於加載,一個用於main game和一個用於battle class。這取決於遊戲的形式和目標。

規劃

現在,我們的基本結構是到位,但一定 還有需要加強的地方。首先,我們就要有一個英雄,一個怪物。我們最好為這兩個角色建構兩個新的類別,取名叫Hero和Monster。

Hero絕對是一個物件,但不會只有一種。如果我們調用一個新的Hero在程式的循環,每次開始將刪除英雄的統計和刷新數據,不是一個好方式。有什麼好的方法來設置這些數據呢?怎麼樣呼叫初始化裡面的Hero類別的靜態方法?其實,初始化變數不屬於英雄,但是當創建它時可以拉進來。用靜態的方式更好。呼叫初始化maingame類別中,需要發送Initialize()到創造了進行更新的英雄物件。要定義hero的變數為public。

namespace RpgTutorial
{
    class Hero
    {
        public int CurrentHealth, MaxHealth, CurrentMagic;
        public int MaxMagic, Strength, Defense, Agility;
        public int Experience, Gold, AttackDamage;
        public string Identifier;
        public bool isAlive;

        public Hero(){}

        public static void Initialize(Hero hero)
        {
            hero.CurrentHealth = 18;
            hero.MaxHealth = 18;
            hero.CurrentMagic = 8;
            hero.MaxMagic = 8;
            hero.Strength = 10;
            hero.Defense = 3;
            hero.Agility = 6;
            hero.Experience = 0;
            hero.Gold = 0;
            Console.WriteLine("What is your Hero's name?");
            hero.Identifier = Console.ReadLine();
            hero.isAlive = true;
            hero.AttackDamage = hero.Strength;
        }
    }

}

在這裡,Hero將設立當初的初始值,但是因為靜態方法不屬於另一個Hero,讓初始值仍保有一開始的值。

要記得如何設置的參數為Hero類別:Initialize(Hero hero)。這不是要求一個新的物件,或者一個新的宣告,這就像一個empty,顯示它可以被傳遞給它的物件期望的方法。如果嘗試將不同類別的物件不止一個來自像myhero英雄類別,將會產生錯誤。

所以現在MainGame需要更新,呼叫Hero類別,並初始化Hero的變數。

namespace RpgTutorial
{
    class MainGame
    {
        Hero myhero;
        Battle battle;
        string answer;

        public MainGame()
        {
            myhero = new Hero();//創立Hero 類別
            Hero.Initialize(myhero);//傳送 myhero到Initialize的Hero

            //可以變動的,不論初始化的是不是呼叫Hero
            BasicGameLoop();
        }

        void BasicGameLoop()
        {
            do
            {
                battle = new Battle();
                Console.WriteLine("Do you want to play again?");
                answer = Console.ReadLine();
            }

            while (answer == "Y" || answer == "y");
        }
    }

}

在註解裡,表非可以在不同的地方使用不同的名稱,同時仍然使用相同的物件。要注意的是一定要宣告Hero,之前的遊戲循環,沒有創建它的一個新的副本。現在myhero將從這裡開始了使用英雄副本,換句話說,這是將調用一個新的Hero。

Monster 類別

Monster類別需要有相同類型的變數作為Hero。不需要一個initialize方法,因為有Monster的多個副本。所以保持Monster類別簡單多了。

namespace RpgTutorial
{
    class Monster
    {
        public int CurrentHealth, MaxHealth, CurrentMagic;
        public int MaxMagic, Strength, Defense, Agility;
        public int Experience, Gold, AttackDamage;
        public string Identifier;
        public bool isAlive;

        public Monster()
        {
            CurrentHealth = 8;
            MaxHealth = 8;
            CurrentMagic = 0;
            MaxMagic = 0;
            Strength = 5;
            Defense = 3;
            Agility = 4;
            Experience = 5;
            Gold = 2;
            Identifier = "Monster";
            isAlive = true;
            AttackDamage = Strength;
        }
    }

}

正如你可以看到剛剛初始化權的構造函數中的變量。這樣一來,當要求它的一個新實例,它將被設置並準備好了。又一切都公開,因為我們需要的一切將我們的戰鬥類的內部使用。

寫死的程式碼

「寫死的程式碼」是一件很遭的情況。寫死的程式碼是把輸出或輸入的相關參數(例如:路徑、輸出的形式、格式) 直接寫死在原始碼中,而不是再次重複使用一個變數的值到程式中。在英雄職業,怪物的class,是寫死的程式碼在所謂認定的結果論裡。這不能真正避免了這一些流程,但要在程序中使用這些變數,而不是直接輸入一個數字英寸,寫死的程式碼不僅使你的程式不夠靈活,而且更難以改變。比方說,我們有一個球是像素25寬,像素 25高。而不是告訴該程式直接寫死值的大小25x25,反而會告訴它要檢查圖片的寬度和高度。所以這就是Design Pattern的用意,雖然這會程式結構分工的更細,但如果不這樣做,當決定改變球的尺寸30X30 時,必須通過整個程式的修改,並更改25X25到30X30。如果將程式的意圖改成告訴它來檢查圖片的寬度和高度,其實只要變數改變,並不會改變程式整個架構。

在一個大型的專案,如果開發一個巨大的商業遊戲,它決定了開始的基本值,之後想改變是非常困難且更費工和開發成本,如果已經發行的遊戲,這不是一個快速和容易解決的程式結構。在設計上,可以像是將參數寫在一個XML裡。寫死的程式碼是會造成日後開發的時間和成本,所以要避免這種寫法。

戰鬥類別

在這一點上,這應該是相當熟悉的。有一個Loop,檢查每一個正確的絛件,並顯示造成多大的損害。得到了特性上的資訊,可以用它來確定類別的東西造成多大的損害或者後續的動作,所以要作出一個建構函數來接受一個Hero和Monster戰鬥類別:

namespace RpgTutorial
{
    class Battle
    {
        public Battle(Hero hero, Monster monster){}
    }

}

現在有完整的存取狀態。另外,本來可以為角色設置health、magic等參數,但是這是很簡單的。現在來開始循環。首先介紹,然後我們循環,將放進新的方法Method,它會透過bool檢查是否存活,而不是health。

namespace RpgTutorial
{
    class Battle
    {
        public Battle(Hero hero, Monster monster)
        {
            Console.WriteLine("{0} is facing a {1}.", hero.Identifier, monster.Identifier);
        }

        public void BattleLoop(Hero hero, Monster monster)
        {
            do
            {
                //實現
            }

            while (hero.isAlive == true && monster.isAlive == true);
        }

    }

}

傳送物件到新的方法,如此一來就能存取這些狀態,現在需要一個方法去顯示狀態:
public void PrintStatus(Hero hero, Monster monster)
{
    Console.Write(@"

  ********************************

     HP/MaxHP   MP/MaxMP

  {0}:   {1}/{2}hp    {3}/{4}mp

  {5}: {6}/{7}hp      {8}/{9}mp

  ********************************

  ",

     hero.Identifier,
     hero.CurrentHealth,
     hero.MaxHealth,
     hero.CurrentMagic,
     hero.MaxMagic,
     monster.Identifier,
     monster.CurrentHealth,
     monster.MaxHealth,
     monster.CurrentMagic,
     monster.MaxMagic
    );

}

再一次傳遞物件到新的方法,在請求時將會印出兩個角色狀態的方法。這是可以運行來仔細檢查並除錯,看看它是否運作正確。首先,需要確保在創建Main game class的new monster,然後需要更新battle類別同時接受myhero和monster。下面是更新MainGame和BattleClass:

namespace RpgTutorial
{
    class MainGame
    {
        Hero myhero;
        Battle battle;
        string answer;

        public MainGame()
        {
            myhero = new Hero();

            Hero.Initialize(myhero);
            BasicGameLoop();
        }

        void BasicGameLoop()
        {
            do
            {

                Monster monster = new Monster();
                battle = new Battle(myhero, monster);
                Console.WriteLine("Do you want to play again?");
                answer = Console.ReadLine();
            }

            while (answer == "Y" || answer == "y");
        }
    }
}

namespace RpgTutorial
{
    class Battle
    {
        public Battle(Hero hero, Monster monster)
        {
            Console.WriteLine("{0} is facing a {1}.", hero.Identifier, monster.Identifier);
            BattleLoop(hero, monster);
        }

        public void BattleLoop(Hero hero, Monster monster)
        {
            do
            {
                PrintStatus(hero, monster);
                Console.ReadLine();
            }

            while (hero.isAlive == true && monster.isAlive == true);
        }

        public void PrintStatus(Hero hero, Monster monster)
        {
            Console.Write(@"

    ********************************

       HP/MaxHP   MP/MaxMP

    {0}:   {1}/{2}hp    {3}/{4}mp

    {5}: {6}/{7}hp      {8}/{9}mp

    ********************************

    ", hero.Identifier,
    hero.CurrentHealth,
    hero.MaxHealth,
    hero.CurrentMagic,
    hero.MaxMagic,
    monster.Identifier,
    monster.CurrentHealth,
    monster.MaxHealth,
    monster.CurrentMagic,
    monster.MaxMagic
   );
        }
    }

}

到目前為止,呈現出一個menu,但真正從這裡算是簡單且順暢。所有要做的是一個printchoice然後再處理的攻擊,這是很基本的遊戲程式結構。但是還是要解決AI。一些更多的技巧和調整,一個相當實用的Battle loop。遊戲的開發,還有更多的元素,是要透過遊戲呈現,聲音,影像,動畫效果,攻擊,防禦,移動。在此介紹基本的遊戲設計觀。

-雲遊山水為知已逍遙一生而忘齡- 電腦神手

2013年12月18日 星期三

C# - Interface 設計模式的靈魂

玩過物件導向,一定少不了Interface這樣的關鍵字,對於熟悉Design Pattern的開發者而言,Interface就像是寶劍一樣,想要對Design Pattern玩到爐火純青,就一定要對它有深入的了解,筆者就來說說這個不可一世的關鍵字。
在C#中Interface是包含類別或結構可實作相關功能的定義。
使用介面時,在類別中包括來自於多重來源的行為。 因為語言不支援類別,多重繼承該功能是重要的屬性。 要模擬結構的繼承,必須使用介面,因為無法從其他結構實際上繼承或類別。

下方是使用interface關鍵字定義一個接口的範例:

interface IEquatable<T>
{
    bool Equals(T obj);

}

依照上面的程式而言,任何類別或實作 IEquatable<T> 介面的結構必須包含符合簽章指定介面的 Equals 方法的定義。 所以在實作 IEquatable<T> 方法包含 Equals 類別的執行個體可以判斷的類別可以計算它是否與另一個相同類別的執行個體是否相等。
IEquatable<T> 的定義為 Equals不會提供的實作。 介面會定義只有簽章。 以這種方式,介面在 C# 類似於所有方法是抽象的抽象類別。 類別或結構可以實作多個介面,而且類別可以繼承只有單一類別,摘要。 因此可以使用介面並有多個來源的行為。
介面可包含方法、屬性、事件、索引子,或者這些四個成員型別的任何組合。 介面不能包含常數、欄位、運算子、執行個體建構函式、解構函式或型別。 Interface成員會自動是公用的,因此,Interface不能包含任何存取修飾詞。 成員不能是static。
若要實作介面成員,實作的類別中對應的成員必須是public,不是static,而且具有相同名稱和簽章與介面成員。
當類別或結構實作介面時,類別或結構必須為所有成員提供實作介面來定義。 介面不提供類別或結構可繼承,如果基底類別實作介面,從基底類別衍生的任何類別繼承該實作。
下列範例顯示 IEquatable<T> 介面的實作。 實作的類別,則為 Car,必須提供 Equals 方法的實作。

public class Car : IEquatable<Car>
{
    public string Make { get; set; }
    public string Model { get; set; }
    public string Year { get; set; }

    // Implementation of IEquatable<T> interface
    public bool Equals(Car car)
    {
        if (this.Make == car.Make &&
            this.Model == car.Model &&
            this.Year == car.Year)
        {
            return true;
        }
        else
            return false;
    }

}

類別的屬性和索引子可能定義在介面中定義的屬性或索引子的額外的存取子。 實作介面的類別可以宣告 get 和 集合 存取子中的相同屬性。 不過,如屬性或索引子使用明確實作,則存取子必須相符。以下是微軟列出Interface的屬性:

  • 介面是像抽象基底類別。 任何類別或實作介面的結構都必須實作它所有的成員。
  • 介面無法直接執行個體化。 它的成員會由任何實作介面的類別或結構實作。
  • 介面可以包含事件、索引子、方法和屬性。
  • 介面不包含方法實作。
  • 類別或結構可以實作多個介面。 類別可以繼承基底類別也實作一個或多個介面。
-雲遊山水為知已逍遙一生而忘齡- 電腦神手

2013年12月17日 星期二

C# - MVC之Helper

本文要介紹的是Helper,它是MVC不可或缺的好幫手,可以在MVC視圖中使用自定義的HTML Helpers,HTML Helpers可以減少繁瑣的HTML標籤,並有效的整理HTML的頁面呈現。
HTML Helper是返回一個字串的方法。該字串可以代表任何類型的內容。例如可以使用HTML Helpers函數來成為標準的HTML標記,如HTML的<input>和<img>標記。也可以使用HTML輔助函數來呈現更複雜的內容,如標籤或資料庫的HTML表格。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns= "http://www.w3.org/1999/xhtml ">

<head id="Head1" runat="server">
    <title>Index</title>
</head>
<body>

      <div>

          <% using (Html.BeginForm()){ %>

               <label for="firstName">First Name:</label>

               <br />

               <%= Html.TextBox("firstName")%>

               <br /><br />

               <label for="lastName">Last Name:</label>
               <br />
               <%= Html.TextBox("lastName")%>
               <br /><br />
               <input type="submit" value="Register" />   

          <% } %>
          </div>
</body>

</html>

該Html.BeginForm()Helper方法用於建立在開始和結束的HTML<form>標記。要注意的是Html.BeginForm()方法被調用using語句中。 using語句確保<form>標籤在using塊結束時關閉。

開發者可以使用Html.EndForm()輔助方法來關閉<form>標籤,使用任何方法來建立一個開閉,不用建立一個block。

Html.TextBox(),要呼叫HTML Helper時是採用<%=%>標記,而不是<%%>標記呈現。

<%@ Page Language="C#" AutoEventWireup="false" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Index" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
     <title>Index</title>
</head>
<body>
     <div>
          <form action="http://localhost:33102/" method="post">
               <label for="firstName">First Name:</label>
               <br />
               <input id="firstName" name="firstName" type="text" value="" />
               <br /><br />
               <label for="lastName">Last Name:</label>
               <br />
               <input id="lastName" name="lastName" type="text" value="" />
               <br /><br />
               <input id="btnRegister" name="btnRegister" type="submit" value="Register" />
          </form>
     </div>
</body>

</html>

建立一個HTML Helper最簡單的方法,是創建一個返回字符串的靜態方法。

namespace MvcApplication1.Helpers
{
    public class LabelHelper
    {
        public static string Label(string target, string text)
        {
            return String.Format("<label for='{0}'>{1}</label>", target, text);
        }
    }

}


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index2.aspx.cs" Inherits="MvcApplication1.Views.Home.Index2"%>
<%@ Import Namespace="MvcApplication1.Helpers" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
     <title>Index2</title>
</head>
<body>
     <div>
          <% using (Html.BeginForm())
          { %>
               <%= LabelHelper.Label("firstName", "First Name:") %>
               <br />
               <%= Html.TextBox("firstName")%>
               <br /><br />
               <%= LabelHelper.Label("lastName", "Last Name:") %>
               <br />
               <%= Html.TextBox("lastName")%>
               <br /><br />
               <input type="submit" value="Register" />
          <% } %>
     </div>
</body>

</html>

要創建一個標準HTML Helper 運作方式如同ASP.NET MVC  framework,需要建立extension methods。extension methods能夠以現有的類添加新的方法。建立一個HTML Helper的方法,添加新的方法來通過一個視圖的html屬性所代表的HtmlHelper類別。

namespace MvcApplication1.Helpers
{
    public static class LabelExtensions
    {

        public static string Label(this HtmlHelper helper, string target, string text)
        {
            return String.Format("<label for='{0}'>{1}</label>", target, text);
        }
    }

}

當建立一個extension methods之後,並成功地構建應用程序,extension methods會出現在Visual Studio的自動類別清單裡。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index3.aspx.cs" Inherits="MvcApplication1.Views.Home.Index3" %>
<%@ Import Namespace="MvcApplication1.Helpers" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
     <title>Index3</title>
</head>
<body>
     <div>
          <% using (Html.BeginForm())
          { %>
               <%= Html.Label("firstName", "First Name:") %>
               <br />
               <%= Html.TextBox("firstName")%>
               <br /><br />
               <%= Html.Label("lastName", "Last Name:") %>
               <br />
               <%= Html.TextBox("lastName")%>
               <br /><br />
               <input type="submit" value="Register" />
          <% } %>
     </div>
</body>

</html>

在Web開發之中,Helper可以運用在像是「下拉式連動選單」這樣的設計上。唯一特別的事,此方式是採用MVC的架構導向。所以在設計上,可以是更有架構,程式也更為簡潔。

-雲遊山水為知已逍遙一生而忘齡- 電腦神手