2012年5月30日

TFS - 使用NuGet搭配版本控制

支援版本

  • NuGet 1.6 up

我們都知道,軟體開發絕對不可以少的東西之一,就是版本控制,無論是使用小烏龜,或是TFS,使用版本控制,好處絕對比想像中的多;那怎樣的版本控制才叫好呢!?支援的多,支援的功能天花墜無與倫比!?( 讓我想到了以前李立群的Konica底片廣告… ),但不管怎樣,版本控制有一個很重要的要點就是能讓使用者"簽出後,立即能用",而小弟也遵循著這個教誨;之前,在NuGet還沒流行時XDD,也都沒有碰到多大問題;而NuGet出現的一段時間後,小弟還是沒有多大問題XDD( 因為只有小弟我在用XDD ),到如今,NuGet已經變成耳熟能詳,超級簡單好用的工具了,而且因為簡單,大家都會用!,所以也出現了一些災難。

喔,對了,小弟後來也搜尋到,Will保哥也有一篇很完整的文章,如果大家已經看過保哥的文章,就可以不用往下看啦XDD,節省時間去玩Diablo 3吧XDD

NuGet VS TFS

這兩個東西當然不是真的要去VS,因為是完全不一樣的東西啊XDDD;小弟想強調的是,因時代已經有點不一樣了,NuGet的出現,我們不會在像以前一樣,用手動的方式,把dll放到專案裡面 ( 當然,如果NuGet裡面沒有,還是必須手動就是了 );而我們正常在使用TFS時,如果要把檔案或是目錄加入到原始檔控制,TFS會超級佛心的幫我們把*.dll給濾除掉,如下圖。

image

而也因為NuGet的方便,也因為我們沒有再手動的放東西進去,所以,我們會常常忘記了用NuGet加入的dll,就直接按下完成,就很順理成章的把dll略過了,就像下圖,Moq裡面完全沒有Moq的dll了。

image

結果會怎樣!?如果是小弟,小弟弄完後,就會很大聲的通知同組人員,來證明小弟的速度很快,然後就會聽到XX聲( 自動消音 )四起,每個人都會喊"不能執行阿!!"。

當然,這個還算好,倒楣的是那種下班急著簽入,然後簽入後人就跑的那種;( 難怪要提倡持續整合XDDD ),當然,結果就是後面一堆人要收尾= =。

沒錯,因為少了dll阿,所以自然就沒辦法執行了,就像下圖。

image

一堆NuGet的檔案

其次,如果我們把NuGet的東西全部都加入到版控裡面,其實也會佔據非常大的空間,就像下圖,增加一個Json.Net,就會有非常多的東西要簽入。

image

啟用NuGet套件還原

其實NuGet的套件,都會記錄在packages.config這個檔案裏面,而該需要的檔案和dll,都會放在packages目錄下面,所以在還沒有NuGet套件還原之前,如果不想發生上面那種被人罵慘的錯誤,就必須把很多的東西簽入進去,而如果ㄧ些專案是後來才加入版控的,也可能會因為疏失遺漏掉;當然,NuGet的開發人員,也接受到許多的建議(抱怨!?),所以在1.6版本後,加入了一個"啟用NuGet套件還原"的功能。

這個功能,老實說,看到中文名稱,還是猜不出來是做啥的XDD,其實這個功能最大的用處就是,如果今天放置dll等檔案的packages目錄不存在時,在編譯的時候,會自動地幫我們把缺少的packages目錄補齊!

有沒有很酷!?這樣,我們就可以不用怕忘記把dll加入進去,其次我們的版控,也不需要丟一堆東西。

如何做!?

假設我們這邊要新增一個Json.Net。

image

新增完後,自然就多了一堆檔案,我們先不管那些檔案,我們在方案的地方,按下滑鼠右鍵,然後選擇"啟用NuGet套件還原"。

image

然後就會跳出一個視窗,按下確定就可以了。

image

然後再按下確定後就完成了。

image

這時套件就會多了這兩樣東西。

image

接下來,我們從靜止的變更視窗下,選擇所有package目錄下的檔案,並且選則"復原",看是要選擇上面的復原按鈕,或是滑鼠右鍵Menu後的復原按鈕,都可以。

image

其他的檔案,就照常簽入吧。

image

然後在原始檔控制總管,將原本的Package目錄給刪除,當然,不刪除其實也是可以,但既然NuGet會自動重建,這個留著其實也沒甚麼意義,記住,選擇刪除後,也要簽入回去。

image

這時候,如果有其他的使用者,把整個專案簽出來,理所當然,就不會有packages的資料夾,這時候如果編譯,NuGet就自動幫你把缺的東西補齊。

image

我們去本機的目錄去看,可以發現,已經幫我們把packages目錄給建立好了。

image

就這樣,我們就完成了!

後記

我們透過NuGet的套件還原機制,就可以不需要把packages目錄簽入進去,就可以省了很多的空間,其次,我們利用這個機制,也可以有效的預防dll忘記加進去版本控制的風險,也提高了版本控制真正的意義,讓簽出的人,可以馬上迅速地進入工作!最後,其實這個機制也可以搭配Build Server;總之,這個機制,讓NuGet與TFS更加的緊密結合。

參考網址

2012年5月26日

ASP.NET MVC - MVC和Web API之Model Binder的陷阱

其實也不能說陷阱啦,只能說小弟自己太笨,今天又踩進去一次 ( 記得以前也踩進過一次 ),所以決定在這邊紀錄一下,讓自己有個印象深刻的記憶QQ。

先說明一下,這個絕對不是ASP.NET MVC的Bug,只能說這種細節,一不小心就會撞到XDD

Model

首先我們先看看Model,這個Model簡單到爆炸,反正就是有Id、Name、Phone,然後利用DisplayName來定義未來要在View那邊顯示的資訊。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel;

namespace MvcOrz.Models
{
    public class Customer
    {
        [DisplayName("ID")]
        public int Id { get; set; }

        [DisplayName("Name")]
        public string Name { get; set; }

        [DisplayName("Phone")]
        public string Phone { get; set; }
    }
}

就這麼簡單,接下來我們看一下Controller。

Controller

這個Controller其實也很簡單,就是兩個Action,第一個Action Index很簡單,就只是顯示出畫面,第二個Action Index2則會傳入兩個參數,第一個是id,也就是會去收網址後面的值,例如/Home/Index2/123,就會把123帶進去到id裡面;而第二個則會把View那頁Post或是其他方式進來的資料,自動轉成Customer Model。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcOrz.Models;

namespace MvcOrz.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Index2(int id,Customer customer)
        {
            customer.Id = id;
            return View();
        }

    }
}

基本上這還滿簡單的,接下來我們看一下View。

VIew

View也簡單到一個不行,幾乎都是利用HtmlHelper來利用Model產生需要的東西,但有個地方要注意到的,就是在Html.BeginForm的地方,我們會多加上一個RouterValue,並設定id = 1;

@model MvcOrz.Models.Customer

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
    @using (Html.BeginForm("index2","Home", new { id = "1" }))
    {
        <fieldset>
            <legend>文案</legend>
            <dl>
                <dt>@Html.LabelFor(model =>model.Id)</dt>
                <dd>@Html.EditorFor(model => model.Id)</dd>

                <dt>@Html.LabelFor(model => model.Name)</dt>
                <dd>@Html.EditorFor(model => model.Name)</dd>

                <dt>@Html.LabelFor(model => model.Phone)</dt>
                <dd>@Html.EditorFor(model => model.Phone)</dd>
            </dl>
        </fieldset>
        <input type="submit" value="Send" />
    }
    </div>
</body>
</html>

就這樣!非常簡單,而且我相信各位大家應該也猜得出梗了。

第一個

我們把整個專案執行起來,並且在Name和Phone填入3。

image

到後面我們利用中斷偵錯,我們會發現,沒錯!,第一個參數id,變成3了!,但實際上,我們預期的卻是1阿!

image

其實這個沒有甚麼好奇怪,因為ASP.NET MVC的預設就是這樣,會依據Model對應到的屬性名稱優先,不過,通常也不會有人會寫這樣的程式碼,因為id通常都是不能改的,所以通常會寫成第二個範例的樣子。

第二個

接下來第二個範例,我們把id拿掉了。

image

的確,這樣第一個id參數就會如預期的變成1了。

image

但是好景不長XDD,Customer裡面的Id也變成1了。

image

這樣子的狀況,某方面來講也不算是問題的,因為通常網址後面帶的id,也和我們準備要改的資料id是同樣的 ( 會在BeginForm裡面的RouterValue值換成當前的id ),其次為了安全性,我們也通常都會再加上Exclue,來過濾id,例如下面程式碼,所以幾乎不會有問題。

public ActionResult Index2(
    int id, [Bind(Exclude = "Id")]Customer customer)
{
    customer.Id = id;
    return View();
}

第三個就比較容易犯錯…

第三個

第三個範例,是笨笨小弟我今天踩到的地雷,這是一個Silverlight裡面的一段程式片段,程式的畫面如下。

image

當然這裡不會全部解釋,主要的地方是更新的區塊,小弟我利用WebRequest來對Web Service進行Http的PUT命令,詳細的程式碼小弟就不解釋了,反正重點在於,我先用取得id這個TextBox的值( idTbx.Text ),因為要知道id才能進行Update,接下來,也從TextBox裡面取得要變更的Name和Phone,然後設定WebRequest的URI為Web Service再加上剛剛取得的id ( 例如URI為api/Customer/1 ),然後就進行更新,程式碼在下面,其實也不用看懂,因為重點不是程式碼XD。

    private void updateButton_Click(object sender, RoutedEventArgs e)
    {
        string id = idTbx.Text;

        Customer customer = new Customer();

        customer.Name = nameTbx.Text;
        customer.Phone = phoneTbx.Text;


        #region 使用WebRequest

        WebRequest webRequest = WebRequestCreator.ClientHttp.Create(new Uri(_url + "/" + id));
        webRequest.ContentType = "application/json";
        webRequest.Method = "PUT";
        webRequest.BeginGetRequestStream(requestAsyncCallback =>
        {
            Stream requestStream = webRequest.EndGetRequestStream(requestAsyncCallback);

            string json = JsonConvert.SerializeObject(customer, Formatting.Indented);
            byte[] buffer = System.Text.Encoding.Unicode.GetBytes(json);
            requestStream.Write(buffer, 0, buffer.Length);
            requestStream.Close();

            webRequest.BeginGetResponse(responseAsyncCallback =>
            {
                WebResponse webResponse = webRequest.EndGetResponse(responseAsyncCallback);
                using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
                {
                    string result = reader.ReadToEnd();
                    this.Dispatcher.BeginInvoke(() =>
                    {
                        MessageBox.Show(result);
                    });
                }
            }, null);
        }, null);

        #endregion
    }

我們填入了這些值,希望針對id 1的Customer,將Name改為1,Phone也改為1。

image

結果看一下中斷點,變成0!!。

image

為什麼會這樣呢?ASP.NET MVC Web API裡面,第一個參數,應該就是URI後面的參數阿,我們的URI為api/Customer/1,那這裡的id應該為1阿,為什麼會為0呢?再看一下Customer變數。

image

沒錯Customer也為0,其實答案很簡單,那是因為在Silverlight裡面的程式碼段落,我們只有設定Customer.Name和Customer.Phone,而沒有設定Customer.Id,所以Customer.id就為0,如第一個範例一樣,預設上,ASP.NET MVC的模型繫節,會以Model為主,如果沒有對應到,才會以網址參數對應,但如果在Silverlight裡面補上Customer.Id = id的程式碼;就會讓Put的第一個參數id和Customer裡面的Id都有值了。

但其實到這邊,不管有無機率踩到。最好的方法,要嘛Model不要取名為id,要嘛把Router改一下。( 在Global.asax.cs檔案裡面 )

image

將下面的"{controller}/{action}/{id}", 的{id}改成新的名字,例如routerValueId。( 如果是Web Api,也要去改Web Api的地方,Web Api在這行 routeTemplate: "api/{controller}/{id}" ),下圖是ASP.NET MVC的地方,另外,別忘了後面的id也要改到,這樣如果沒有填入id值,就會自動幫我們設定預設值。

image

改成如下程式碼 ( 以下為MVC範例。 )

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{routerValueId}", // URL with parameters
        new { controller = "Home", action = "Index", routerValueId = UrlParameter.Optional } // Parameter defaults
    );

}

當然,別忘了所有Action的參數也全部都要改,例如Put的程式碼片段,但記得全部的地方都要改阿!

public void Put(int routerValueId, Customer customer)
{
    customer.Id = routerValueId;
    if (!_repository.Update(customer))
    {
        //如果找不到,就拋出HTTP的Response例外,內容是尋找不到,也就是404
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
}

除非很不幸的,連改了名子都會和Model的屬性撞到;不然這樣幾乎就不會有任何的問題了。

後記

其實真正的重點是希望大家能了解Model Binder的一些特性,其實只要了解了,但不了解的話,也真的是欲哭無淚,找不到問題,所以也在這邊po出來,希望大家能注意一下,Model Binder的一些小細節。

2012年5月24日

ASP.NET MVC Web API - 利用jQuery進行CRUD! 番外篇 丟去Azure吧!

5/31 新增Namespace和設定錯誤訊息。

前面花了很多時間寫了三篇ASP.NET MVC Web API的教學,這篇我們來稍微玩一下輕鬆的話題,不然Windows Azure快要過期了QQ,再不趕快給他玩一下,太對不起自己了。

好,這篇其實很簡單,我們主要的目標是希望把前面寫的東西佈署到雲端上去;當然同樣的技巧也適用於ASP.NET和ASP.NET MVC。

或許有人會說,我之前的程式都沒有寫,那我要怎樣練習!?,首先,如果你沒有申請到帳號,基本上也沒機會傳到Azure上面去,但目前台灣地區還沒開放( 寫這篇文章的時間是5/24,未來這篇文章不見得會記得更新,所以要練習的朋友,可以去看看目前開放了沒 ),台灣地區目前也快要開放了,大家可以稍等一下;如果真的受不了,想和暗黑破壞神3一樣,選擇美服的玩家,也請注意,需要當地的手機,才能進行驗證;但不管怎樣,模擬器都還是可以正常運作的。

增加雲端專案

下面是我們之前已經寫好的程式,如果沒有寫的人,可以自己新增一個ASP.NET 或是ASP.NET MVC的專案。

image

然後我們新增一個專案。

image

接下來,我們選擇雲端的專案,也請記得,要把專案名稱改成有意義的。

image

這裡甚麼都不用選,直接按下確定就可以了,因為我們不需要再多增加其他的專案。

image

完成後會如下圖。

image

接下來,我們要把現有的MvcWebApiCRUDDemo這個專案,設定成Windows Azure的Web Role;選擇Windows Azure專案底下的角色,然後按下滑鼠右鍵,選擇加入,在選擇方案中的Web角色專案。

image

接下來,按下確定就可以了,原本的我們寫好的Web API就會和Windows Azure的Web Role進行關聯。

image

完成後就會如下圖。

image

接下來,我們可以Run一下模擬器看看,有沒有任何問題。

image

增加NameSpace ( 2012/5/31 新增 )

到這邊,就如上圖一樣,其實就可以順利的運作在Azure了,但是,那是因為整個應用程式裡面也沒用到Azure Storage,所以當然可以順利執行,但假設今天想利用Table儲存的話呢?就需要添加三個NameSpace。

image

增加DiaqnosticMonitorTraceListener Class ( 2012/5/31更新 )

另外,平常沒事就沒事,如果有bug,在雲端的程式其實是很難除錯的,所以我們可以在Web.Config加上DiaqnosticmonitorTraceListener Class,未來我們就可以在程式裡面,Trace Log到Azure Storage。( 這邊就沒示範了,有興趣的可以參考這篇。 )

<configuration>
    <system.diagnostics>
        <trace>
            <listeners>
                <add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                  name="AzureDiagnostics">
                </add>
            </listeners>
        </trace>
    </system.diagnostics>

基本上,如果一開始就是開Cloud專案的話,也會自動的修改Web Role專案的Web.Config喔。

如果沒有Azure帳號,那就只能測試到這邊為止了,接下來,我們來看看怎樣放到Windows Azure。

部屬到Windows Azure

我們選擇Windows Azure專案,並且選擇封裝,當然如果想使用"發行"也是可以的,但使用發行需要做比較多的設定,大家可以參考這篇

image

接下來,按下封裝就可以,這邊有服務組態和組件組態可以設定,這些設定其實會對應到Windows Azure的設定檔,但這裡再談這個就會離題太遠了,基本上,如果沒有特別修改設定,用預設的就可以。

image

完成後,就會套出此視窗,然後Visual Studio會幫我們把東西都給包好。

image

接下來,我們在Windows Azure管理平台的首頁,可以看到新增託管服務。

image

然後我們就要打一堆東西,比較值得注意的是,不管是佈署到預備環境還是生產環境,只要佈署上去,就開始收費;另外,像小弟我佈署上去的時間點剛好是五十幾分,所以過了整點,就算兩個小時的費用了…

image

按下確定後,會出現一個警告,但不用擔心,這只是在告訴我們,如果只設定一個Web Role,可能沒辦法達到99.95%的高可用性 ( 簡單的說,就是有可能遇到服務中斷的情況啦 ),所以微軟建議我們使用兩個Web Role..當然,錢也變成兩倍。

image

接下來就是進入漫長的等待,通常需要10分鐘左右,等到資訊變成下圖,就表示已經完成了。

image

完成後,我們就可以利用剛剛設定好的網址進行測試,看看有沒有問題,當然,如果有DNS,也可以自行設定轉址的動作。

image

就這樣,整個就佈署上去了。

後記

當然,這是一個很簡單的程式,所以其實不會遇到甚麼很大的問題,所以這邊也只簡單的介紹基本的原則,未來如果剛好有碰到不好處理的案子,小弟我再放上來給大家參考吧=V=。

參考資料

ASP.NET MVC Web API - 利用jQuery進行CRUD! (三) View篇

支援版本

  • ASP.NET MVC 4 Beta

終於進入到View篇了,本來是預計一天把三篇寫完的,結果沒想到變成一個禮拜一篇= =,現在終於知道連載的辛苦了,富木堅,對不起以前都一直罵你XDD;回到主題,上一篇我們介紹到Controller,也把整個Web Service完成了,但是我們還沒講到要怎樣利用JavaScript ( 利用jQuery這個函式庫 )對Web Service進行呼叫阿!?所以我們這篇來談談View的部分。

jQuery

小弟就不再這邊詳細的介紹jQuery了,我相信很多人應該都會使用,這邊只簡單的介紹一下jQuery是做甚麼的;jQuery 是一個非常方便,快速,程式碼又簡潔的JavaScript函式庫,原本我們用JavaScript來進行DOM物件的尋找、處理事件、動畫、瀏覽器版本還有Ajax等等,都會寫非常多的程式碼,所以就有位天才John Resig,寫了jQuery來大幅的簡化,總之,就是一個好物就是了;如果不會jQuery,可以參考一下jQuery的官網或是暗黑前輩的超完整教學,所以這部分小弟就不多介紹嚕。

Knockout

這裡必須還要再提一個東西,就是Knockout,他也是一個JavaScript的程式庫,不過不用擔心,小弟沒那麼慘忍XDD,一下要K Repository Patten,一下要看ASP.NET MVC Web API,一下又要看jQuery,所以這篇文章小弟不會用到Knockout;但為什麼要提到這個呢!?那是因為目前這個東西也正是被納入到ASP.NET MVC裡面,而且官方的範例中,就是大量地使用到這個程式庫,Knockout主要的用處是利用MVVM模式,來繫節畫面上的UI;詳細可以參考暗黑前輩的Knockout這篇文章,或是官網,或是Andy前輩的FAQ Book;不過在一次的重申,這篇文章不會用到Knockout,所以可以放鬆心情的去讀這幾位前輩的文章=v=。

View

前面鋪完路後,我們終於要正式開始撰寫View了,不知道大家還記不記得,第一章的時候,那個空白的圖?那時候我們建立起View後,並沒有在View裡面添加甚麼,現在我們終於要開始加上一些東西了;首先,我們可以先打開如下圖的檔案,這就是我們第一章就準備好的檔案。

image

我們首先先準備一下畫面,我們預期的畫面如下圖,基本上和官網的差不多 ( 官網範例沒有Delete喔XDD )。

image

HTML

接下來,HTML要怎樣寫哩,其實也沒甚麼特別的,就是利用了一些HTML5的標籤 ( 畢竟已經是HTML5的時代了 ),然後準備好一個Table,來顯示資料,並且準備一些輸入欄位,以便後續的CRUD( 這裡範例是用DIV標籤來包輸入欄位,當然也可以用DD、DT、或是li等標籤,看個人喜好吧 ),當然,我們也要準備一些 Button來觸發事件,所以我們準備了很多的Button ( 不是Submit按鈕喔!!兩個是有差異的。 ),來觸發各種事件,完成大致上如下。( 眼力好的人,可能已經會發現Button裡面已經有寫準備觸發的事件的Function名稱了,我們等下就會把這些Function建立起來了喔! )

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>

<body>
    <div id="body">
        <section class="content-wrapper main-content">
            <h3>Contacts</h3>
            <table>
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Name</th>
                        <th>Phone</th>
                    </tr>
                </thead>
                <tbody id="customers">
                </tbody>
            </table>
        </section>
        <section id="detail" class="content-wrapper">
            <h3>View Contact</h3>
            <label for="contactId">ID</label>
            <input type="text" title="ID" id="contactId" size="5" />
            <input type="button" value="Get" onclick="find();" />
            <div>
                <label for="name">Name</label>
                <input type="text" title="Name" id="name" />
            </div>
            <div>
                <label for="phone"> Phone</label>
                <input type="text" title="Phone" id="phone" />
            </div>
            <div>
                <input type="button" value="Update" onclick="update();" />
                <input type="button" value="Delete" onclick="del();" />
                <input type="button" value="Add New" onclick="add();" />
            </div>
        <div>
            <p id="status"></p>
        </div>
        </section> 
    </div>
</body>
</html>

接下來,我們稍微弄一下美化吧,所以我們利用CSS進行美化,( 再次強調,不要用HTML做為美化的工具,美化的職責應該是由CSS負責的喔!! )。

CSS

好,不用擔心,我們沒有用到CSS3,下面是個很簡單的CSS,把CSS放到head標籤底下;CSS的內容也超簡單,基本上也只是把table、tr、th、td上個顏色,畢竟小弟現在不是要寫CSS的筆記,所以稍微設定一下而已;另外,小弟特別把從頭擷取index.cshtml的程式碼,我想這樣大家會比較了解CSS要放到哪邊。

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>

<style type="text/css">
    table
    {
        border: 1px solid #000;
        border-collapse: collapse;
        color: #666666;
        min-width: 200px;
    }
    
    tr
    {
        border: 1px solid #000;
        line-height: 25px;
    }
    
    th
    {
        background-color: #B1C3CC;
        color: #000;
        font-size: 13px;
        text-align: left;
    }
    
    th, td
    {
        padding-left: 5px;
    }
</style>

</head>

完成後,就會變成這樣,有沒有有沒有,變漂亮了吧!!( 好吧,其實也沒漂亮到哪去…但我們這篇的重點是jQuery和Web Service,小細節就不要計較了XDD)。

image

JavaScript & jQuery

好,完成畫面後,就可以隨便亂點了,然後就會在偵錯工具出現這些錯誤( 記得偵錯工具要打開…),這當然很正常,因為我們JavaScript還沒開始寫嘛。

image

接下來我們開始寫JavaScript,以下是整個JavaScript,我們可以把整個JavaScript放到head標籤裡面,我們後面會針對細節做介紹。 ( 好的,我知道一些JavaScript放在head,效能等等之類的問題,但是,這不是我們這篇的重點=v= )

<script src="@Url.Content("~/Scripts/jquery-1.6.2.js")" type="text/javascript"></script>
<script type="text/javascript">

    //清空status區段
    function clearStatus() {
        $('#status').html('');
    }

    var API_URL = "api/Customer/";

    //增加資料
    function add() {

        clearStatus();

        var json = JSON.stringify({ name: $("#name").val(), phone: $("#phone").val() });

        $.ajax({
            url: API_URL,
            cache: false,
            type: 'POST',
            contentType: 'application/json; charset=utf-8',
            data: json,
            statusCode: {
                201 /*Created*/: function (data) {
                    getAll();
                }
            }
        });
    }

    //尋找資料
    function find() {

        clearStatus();

        var id = $('#contactId').val();
        if (id != "") {
            $.getJSON(API_URL + id,
            function (data) {
                $("#customers tr").remove();
                $("#customers").append("<tr>" +
                          "<td>" + data["Id"] + "</td>" +
                          "<td>" + data["Name"] + "</td>" +
                          "<td>" + data["Phone"] + "</td>" +
                          "</tr>");
            })
            .fail(
            function (xhr, textStatus, err) {
                $('#status').html('Error: ' + err);
            });
        } else {
            getAll();
        }
    }

    //更新資料
    function update() {
        clearStatus();

        var id = $('#contactId').val();

        var json = JSON.stringify({ name: $("#name").val(), phone: $("#phone").val() });

        $.ajax({
            url: API_URL + id,
            cache: false,
            type: 'PUT',
            contentType: 'application/json; charset=utf-8',
            data: json,
            success: function () { getAll(); }
        })
        .fail(
        function (xhr, textStatus, err) {
            $('#status').html('Error: ' + err);
        });
    }
    
    //刪除指定資料
    function del() {
        clearStatus();

        var id = $('#contactId').val();

        $.ajax({
            url: API_URL + id,
            cache: false,
            type: 'DELETE',
            contentType: 'application/json; charset=utf-8',
            //data: json,
            success: function () { getAll(); }
        })
        .fail(
        function (xhr, textStatus, err) {
            $('#status').html('Error: ' + err);
        });
    }
    
    //取得所有資料
    function getAll() {
        clearStatus();

        //利用Get方式取得。
        $.getJSON(API_URL,
        function (data) {
            $("#customers tr").remove();
            for (i = 0; i < data.length; i++) {
                $("#customers").append("<tr>" +
                              "<td>" + data[i].Id + "</td>" +
                              "<td>" + data[i].Name + "</td>" +
                              "<td>" + data[i].Phone + "</td>" +
                              "</tr>");
            }
        })
    .fail(
        function (xhr, textStatus, err) {
            $('#status').html('Error: ' + err);
        });
    }

    //開始時,先把資料讀取進來
    $(document).ready(function () {
        getAll();
    });


</script>

接下來,我們針對每個細部,做一個講解,有講不好的地方,不要鞭我喔><。

清空

我們的第一個function,主要用途是清除status的資訊,在HTML裡面,有一段會顯示錯誤訊息等資訊,所以我們這邊也要準備一下,清除這些資訊的Function。

//清空status區段
    function clearStatus() {
        $('#status').html('');
    }

接下來繼續。

取得全部

我們先來介紹取得全部的這個Funciton,也就是getAll(),我們這邊利用jQuery的getJSON API進行處理,這個API預設會用HTTP的GET命令;當順利取得資料的時候,就會把HTML table裡面的東西移除掉,然後再利用迴圈的方式,把取得的JSON資料和HTML標籤td,一起塞到Table裡面去。

//取得所有資料
function getAll() {
    clearStatus();

    //利用Get方式取得。
    $.getJSON(API_URL,
    function (data) {
        $("#customers tr").remove();
        for (i = 0; i < data.length; i++) {
            $("#customers").append("<tr>" +
                          "<td>" + data[i].Id + "</td>" +
                          "<td>" + data[i].Name + "</td>" +
                          "<td>" + data[i].Phone + "</td>" +
                          "</tr>");
        }
    })
.fail(
    function (xhr, textStatus, err) {
        $('#status').html('Error: ' + err);
    });
}

然後我們來看看新增。

ADD

第二段我們要講的是ADD,但我們談論ADD之前,我們要先設一個變數,負責記錄網址位置,也就是API_URL;接下來,因為是新增,所以我們會需要準備傳遞資料,所以我們利用JSON.stringify來將我們填入表單的資料,轉成JSON格式。然後我們就要利用jQuery的ajax API來對Web Service進行呼叫;還記得嗎?HTTP的POST就是新增的意思,所以我們Type會設定POST,並且等傳回201時,執行getAll()這個Function。

var API_URL = "api/Customer/";

    //增加資料
    function add() {

        clearStatus();

        var json = JSON.stringify({ name: $("#name").val(), phone: $("#phone").val() });

        $.ajax({
            url: API_URL,
            cache: false,
            type: 'POST',
            contentType: 'application/json; charset=utf-8',
            data: json,
            statusCode: {
                201 /*Created*/: function (data) {
                    getAll();
                }
            }
        });
    }

這樣ADD就完成了,下圖是執行結果,我們填入AA、AA的資料 ( Phone欄位沒有驗證,我真的知道><,是我的錯。 ),按下AddNew後,畫面會自動更新Table,出現第四筆資料;而下面的分析工具可以看到,真的送出了POST。

image

Find

接下來是尋找資料,其實尋找資料和getAll()很像,就不多加敘述了,但比較特別的是,我們會在網址 (API_URL)後面加上id,來尋找到想要找到的那一筆;如果沒有找到,就會在status區塊報錯。

//尋找資料
function find() {

    clearStatus();

    var id = $('#contactId').val();
    if (id != "") {
        $.getJSON(API_URL + id,
        function (data) {
            $("#customers tr").remove();
            $("#customers").append("<tr>" +
                      "<td>" + data["Id"] + "</td>" +
                      "<td>" + data["Name"] + "</td>" +
                      "<td>" + data["Phone"] + "</td>" +
                      "</tr>");
        })
        .fail(
        function (xhr, textStatus, err) {
            $('#status').html('Error: ' + err);
        });
    } else {
        getAll();
    }
}

執行結果如下,table會更新成尋到到的那筆,在下面的偵錯視窗可以看到,利用了Get。

image

Update

更新資料和新增資料很像,一樣是利用JSON.stringify來轉換成JSON格式,然後用PUT來進行傳送。

//更新資料
function update() {
    clearStatus();

    var id = $('#contactId').val();

    var json = JSON.stringify({ name: $("#name").val(), phone: $("#phone").val() });

    $.ajax({
        url: API_URL + id,
        cache: false,
        type: 'PUT',
        contentType: 'application/json; charset=utf-8',
        data: json,
        success: function () { getAll(); }
    })
    .fail(
    function (xhr, textStatus, err) {
        $('#status').html('Error: ' + err);
    });
}

執行結果如下,我們ID、Name、Phone都填好後,按下Update就會更新資料,並且取得最新的table,我們也可以看到下面的偵錯工具,可以發現到現在是用HTTP PUT命令。

image

Delete

刪除資料更簡單了,因為連JSON都不需要了,我們只要URL配合id,並且送出DELETE的指令就可以了!

//刪除指定資料
function del() {
    clearStatus();

    var id = $('#contactId').val();

    $.ajax({
        url: API_URL + id,
        cache: false,
        type: 'DELETE',
        contentType: 'application/json; charset=utf-8',
        //data: json,
        success: function () { getAll(); }
    })
    .fail(
    function (xhr, textStatus, err) {
        $('#status').html('Error: ' + err);
    });
}

測試一下,我們填入要刪除的ID,並按下Delete按鈕,table就會自動更新,我們也可以從偵錯視窗看到目前使用的是HTTP裡面的Delete命令。

image

畫面載入完成時

這是最後一小段的程式碼,小弟我希望畫面載入完成後,table會再動載入資料進來,所以加了這段。

//開始時,先把資料讀取進來
    $(document).ready(function () {
        getAll();
    });

到這邊就大功告成,我們最後再把整個index.cshtml看一遍吧。

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>

<style type="text/css">
    table
    {
        border: 1px solid #000;
        border-collapse: collapse;
        color: #666666;
        min-width: 200px;
    }
    
    tr
    {
        border: 1px solid #000;
        line-height: 25px;
    }
    
    th
    {
        background-color: #B1C3CC;
        color: #000;
        font-size: 13px;
        text-align: left;
    }
    
    th, td
    {
        padding-left: 5px;
    }
</style>
<script src="@Url.Content("~/Scripts/jquery-1.6.2.js")" type="text/javascript"></script>
<script type="text/javascript">

    //清空status區段
    function clearStatus() {
        $('#status').html('');
    }

    var API_URL = "api/Customer/";

    //增加資料
    function add() {

        clearStatus();

        var json = JSON.stringify({ name: $("#name").val(), phone: $("#phone").val() });

        $.ajax({
            url: API_URL,
            cache: false,
            type: 'POST',
            contentType: 'application/json; charset=utf-8',
            data: json,
            statusCode: {
                201 /*Created*/: function (data) {
                    getAll();
                }
            }
        });
    }

    //尋找資料
    function find() {

        clearStatus();

        var id = $('#contactId').val();
        if (id != "") {
            $.getJSON(API_URL + id,
            function (data) {
                $("#customers tr").remove();
                $("#customers").append("<tr>" +
                          "<td>" + data["Id"] + "</td>" +
                          "<td>" + data["Name"] + "</td>" +
                          "<td>" + data["Phone"] + "</td>" +
                          "</tr>");
            })
            .fail(
            function (xhr, textStatus, err) {
                $('#status').html('Error: ' + err);
            });
        } else {
            getAll();
        }
    }

    //更新資料
    function update() {
        clearStatus();

        var id = $('#contactId').val();

        var json = JSON.stringify({ name: $("#name").val(), phone: $("#phone").val() });

        $.ajax({
            url: API_URL + id,
            cache: false,
            type: 'PUT',
            contentType: 'application/json; charset=utf-8',
            data: json,
            success: function () { getAll(); }
        })
        .fail(
        function (xhr, textStatus, err) {
            $('#status').html('Error: ' + err);
        });
    }
    
    //刪除指定資料
    function del() {
        clearStatus();

        var id = $('#contactId').val();

        $.ajax({
            url: API_URL + id,
            cache: false,
            type: 'DELETE',
            contentType: 'application/json; charset=utf-8',
            //data: json,
            success: function () { getAll(); }
        })
        .fail(
        function (xhr, textStatus, err) {
            $('#status').html('Error: ' + err);
        });
    }
    
    //取得所有資料
    function getAll() {
        clearStatus();

        //利用Get方式取得。
        $.getJSON(API_URL,
        function (data) {
            $("#customers tr").remove();
            for (i = 0; i < data.length; i++) {
                $("#customers").append("<tr>" +
                              "<td>" + data[i].Id + "</td>" +
                              "<td>" + data[i].Name + "</td>" +
                              "<td>" + data[i].Phone + "</td>" +
                              "</tr>");
            }
        })
    .fail(
        function (xhr, textStatus, err) {
            $('#status').html('Error: ' + err);
        });
    }

    //開始時,先把資料讀取進來
    $(document).ready(function () {
        getAll();
    });


</script>
</head>
<body>
    <div id="body">
        <section class="content-wrapper main-content">
            <h3>Contacts</h3>
            <table>
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Name</th>
                        <th>Phone</th>
                    </tr>
                </thead>
                <tbody id="customers">
                </tbody>
            </table>
        </section>
        <section id="detail" class="content-wrapper">
            <h3>View Contact</h3>
            <label for="contactId">ID</label>
            <input type="text" title="ID" id="contactId" size="5" />
            <input type="button" value="Get" onclick="find();" />
            <div>
                <label for="name">Name</label>
                <input type="text" title="Name" id="name" />
            </div>
            <div>
                <label for="phone"> Phone</label>
                <input type="text" title="Phone" id="phone" />
            </div>
            <div>
                <input type="button" value="Update" onclick="update();" />
                <input type="button" value="Delete" onclick="del();" />
                <input type="button" value="Add New" onclick="add();" />
            </div>
        <div>
            <p id="status"></p>
        </div>
        </section> 
    </div>
</body>
</html>

以上,終於寫完!

後記

寫完的當下,才發現KingKong前輩,和阿源哥哥前輩都有寫過類似的文章 ( 暈倒 ),但不管怎樣,這是小弟自己邊看邊寫的讀書筆記啦><,所以如果小弟沒寫好的地方,也可以去看看前輩們寫的超詳細文章喔!!

參考資料