[WebAPI][jQuery] ASP.NET MVC Web API (2)-從無到有,利用jQuery來進行CRUD


上一篇針對 ASP.NET Web API 做了一個簡單的介紹,如果對概念還不是很清楚的讀者可以在回去看看上一篇的介紹,Web API 即為一種 Web Service ,並尊循了 REST 的設計風格,而其輕量化的服務可以為我們省略很多傳輸上的資源,以筆者的個人經驗來說最近將專案上的某些服務拉上 Web API 實作,整體的運作流程上真的比原本呼叫 Controller 來的快許多,而因為其又遵循了 REST 設計風格,讓筆者可以很輕鬆透過前端的 jQuery 來對我們後端的資料作請求,所以接下來這篇將針對利用 jQuery 發送請求至我們的 Web API 來對資料做 CRUD ,而在操作上有哪些需要注意的地方。

前言

上一篇針對 ASP.NET Web API 做了一個簡單的介紹,如果對概念還不是很清楚的讀者可以在回去看看上一篇的介紹,Web API 即為一種 Web Service ,並尊循了 REST 的設計風格,而其輕量化的服務可以為我們省略很多傳輸上的資源,以筆者的個人經驗來說最近將專案上的某些服務拉上 Web API  實作,整體的運作流程上真的比原本呼叫 Controller 來的快許多,而因為其又遵循了 REST 設計風格,讓筆者可以很輕鬆透過前端的 jQuery 來對我們後端的資料作請求,所以接下來這篇將針對利用 jQuery 發送請求至我們的 Web API 來對資料做 CRUD ,而在操作上有哪些需要注意的地方。

Before Start 建立觀念

從 ASP.NET Web API 的路由設定裡面我們可以看到,預設的路由並沒有包含 {action} 的設定,取而代之的是依照你送出的 HTTP Method 會自動對應至 Controller 內已動詞片語的 Function,就像我們下面看到的,總共分為 Get()、Delete()、Put()、Post(),所以當使用者直接在網址輸入 api/values/ 因為送出的方法為 GET 故 ASP.NET Web API 會自動對應至 Get() 這個 Function,而若今天網址有帶參數,向是這樣 api/values/1 則 Web API 核心會自動對應至 Get(int id) 這個 Function,這些觀念非常重要,讀者應該在一開始就應該建立起這個觀念,因為後續我們透過 jQuery 在進行操作時就會運用到這些概念。

另外我們看一下 Put 和 Post 這兩個個 Function ,參數裡面有一個  [FromBody] 代表著我們的參數不是來自網址,而是來自文件本體的資料,並不是像 Get 那樣在網址用串接的方式,若沒有加上 [FromBody] 則代表為 [FromUrli] 。


    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        public string Get(int id)
        {
            return "Hello Web API";
        }

        // POST api/values
        public void Post([FromBody]string value)
        {
        }

        // PUT api/values/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/values/5
        public void Delete(int id)
        {
        }
    }

動手做做看

1.我們的 Model 欄位如下:


  public partial class Product
  {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Ename { get; set; }
        public string Color { get; set; }
        public string ModifyUid { get; set; }
        public DateTime ModifyTime { get; set; }
  }

2.接下來我們來實作一下我們的 Web API 的四個 CRUD 的 Function,這邊也提供讀者一個小技巧 ASP.NET MVC 內建了自動幫我們產生範本的功能,如下:

透過範本快速建立,會自動幫我們產生對應的 Code ,而產生的程式碼剛好就是我們的 CRUD 的程式碼,而本篇的範例只是將關入帶入所以我們可以完全不用修改它就可以直接來使用,範本 Code 如下:


    public class ProdcutController : ApiController
    {
        private DotblogEntities db = new DotblogEntities();

        // GET api/Prodcut
        public IEnumerable GetProducts()
        {
            return db.Product.AsEnumerable();
        }

        // GET api/Prodcut/5
        public Product GetProduct(int id)
        {
            Product product = db.Product.Find(id);
            if (product == null)
            {
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
            }

            return product;
        }

        // PUT api/Prodcut/5
        public HttpResponseMessage PutProduct(int id, Product product)
        {
            if (!ModelState.IsValid)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }

            if (id != product.Id)
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }

            db.Entry(product).State = EntityState.Modified;

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
            }

            return Request.CreateResponse(HttpStatusCode.OK);
        }

        // POST api/Prodcut
        public HttpResponseMessage PostProduct(Product product)
        {
            if (ModelState.IsValid)
            {
                db.Product.Add(product);
                db.SaveChanges();

                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, product);
                response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = product.Id }));
                return response;
            }
            else
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }
        }

        // DELETE api/Prodcut/5
        public HttpResponseMessage DeleteProduct(int id)
        {
            Product product = db.Product.Find(id);
            if (product == null)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }

            db.Product.Remove(product);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
            }

            return Request.CreateResponse(HttpStatusCode.OK, product);
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }

而這邊有一個需要注意的地方則是回傳的資料型態,除了 Get 以外,其他的 Function 回傳的形態都是 HttpResponseMessage,透過這個型態我們可以回傳 StatusCode 或是自動我們的錯誤訊息...等,像我們常見的有 200 成功、404 NotFound、400 BadRequest ... 等,因為前面說過了 Web API 本身就是種 HTTP Service 而也遵循了 REST 風格,所以我們應該將操作的本質回到 HTTP 上,利用 Header 的資訊來告訴我們前端的瀏覽器或是應用程式操作的狀況是正常還是有異常的。

3.再來我們就建立立一個簡單的 View 來當我們前端的應用程式頁面


@{
    ViewBag.Title = "Demo";
}



<div id="body" style="padding: 10px;">
    <h2>利用jQuery透過Web API進行CRUD</h2>
    <table>
        <thead>
            <tr>
                <th style="padding:10px; background-color:gray;">ProductId</th>
                <th style="padding:10px; background-color:gray;">ProductName</th>
                <th style="padding:10px; background-color:gray;">Ename</th>
                <th style="padding:10px; background-color:gray;">Color</th>
            </tr>
        </thead>
        <tbody id="dataList">
        </tbody>
    </table>
    <hr />
    <table>
        <tr>
            <td style="text-align: right;">ProdcutId</td>
            <td>
                <input type="text" id="proudctId" />
            </td>
        </tr>
        <tr>
            <td style="text-align: right;">ProductName</td>
            <td>
                <input type="text" id="prodcutName" />
            </td>
        </tr>
        <tr>
            <td style="text-align: right;">Ename</td>
            <td>
                <input type="text" id="Ename" />
            </td>
        </tr>
        <tr>
            <td style="text-align: right;">Color</td>
            <td>
                <input type="text" id="Color" />
            </td>
        </tr>
    </table>
    <input type="button" id="post" value="new" />
    <input type="button" id="get" value="get" />
    <input type="button" id="delete" value="delete" />
    <input type="button" id="update" value="put" />
</div>

畫面如下:

畫面非常簡單,而也提醒一下讀者CSS的部份還是建議寫在 .css 的檔案裡面,盡量不要再 View 裡寫 CSS,不過這邊竟然只是單純做個 Demo 而已所以就沒特別拆開了(被歐),然而當我們按下Get代表會讀取資料,Delete 會將資料刪除,Put 代表更新資料,而 New 則為新增資料,剛好是我們前面所說的 CRUD 四種資料庫操作方式。

4.呼呼 ~ 終於到了這篇文章的重點了,我們先回顧一下剛剛透過範本產生的 Web API Controller 裡面有:GetProducts、GetProduct、PostProduct、PutProduct、DeleteProduct,這五個 Function,不過不是說好 CRUD 嗎?怎麼多了一個,因為 Get 有兩種,一個是取得單筆資料,另外一個則是取得所有資料,所以接下來我們就可以利用 jQuery 依照送出的 HTTP Method 不同而呼叫個別的 Function 來進行資料處理了。

GetAll - 取得所有資料

取得所有資料,所以顧名思義我們需要利用 GET 的方式來取得資料,而因為是所有資料所以不用傳入參數,這邊利用了 jQuery 的 $.getJSON 來做處理,預設會送出 GET 的請求,且 Content-Type 為 application-json 格式。

jQuery 部分:


        function FindAll() {
            $.getJSON("/api/Product", function (data) {
                $("#dataList").empty();
                for (var i = 0; i <= data.length - 1; i++) {
                    $("#dataList").append("<tr>" +
                        "<td>" + data[i].Id + "</td>" +
                        "<td>" + data[i].Name + "</td>" +
                        "<td>" + data[i].Ename + "</td>" +
                        "<td>" + data[i].Color + "</tr>");
                }
            });
        }

Web API 部分:


        public IEnumerable GetProducts()
        {
            return db.Product.AsEnumerable();
        }

Get - 取得單筆資料

取得單筆資料跟取得所有資料很像,差別只在於我們多了一個 id 的參數,Web API 核心會對應至有參數的那個 Get Function,而若找不到產品資料則會回應 404 - NotFound 的狀態碼至 Client 端,所以在 jQuery 的部份利用 .fail 來攔截錯誤狀態並顯示。

jQuery 部分:


        function Find() {
            var id = $("#proudctId").val();
            if (id.length == 0) return false;

            $.getJSON("/api/Product/" + id, function (data) {
                $("#dataList").empty();
                $("#dataList").append("<tr>" +
                        "<td>" + data.Id + "</td>" +
                        "<td>" + data.Name + "</td>" +
                        "<td>" + data.Ename + "</td>" +
                        "<td>" + data.Color + "</td></tr>");

            }).fail(function (xhr, Status, errMsg) {
                alert(xhr.statusText);
            });
        }

Web API 部分:


        public Product GetProduct(int id)
        {
            Product product = db.Product.Find(id);
            if (product == null)
            {
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
            }

            return product;
        }

Creat - 新增資料

而在新增這邊我們利用 $.ajax 來操作,比較需要注意的地方是需將 type 設定成 POST、且 contentType 屬性為 application/json 格式,另外在 Web API 這邊我需要小小做個修改,因為是新增資料,當然我們不希望這筆資料可以透過網址來傳送,所以需在參數前加 [FromBody] 來告訴 Web API 這個參數只能來自文件本體而不能是利用網址傳送過來的。

jQuery 部分:


        function Creat() {
            var jsonData = JSON.stringify({ Name: $("#prodcutName").val(), Ename: $("#Ename").val(), Color: $("#Color").val() });
            $.ajax({
                url: "/api/Product/",
                type: "POST",
                contentType: "application/json; charset=utf-8",
                data: jsonData,
                statusCode: {
                    201: function (data) {
                        FindAll();
                    }
                }
            });
        }

Web API 部分:


 public HttpResponseMessage PostProduct([FromBody]Product product)
        {
            if (ModelState.IsValid)
            {
                db.Product.Add(product);
                db.SaveChanges();

                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, product);
                response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = product.Id }));
                return response;
            }
            else
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }
        }

Update - 更新資料

更新這邊跟新增大同小異,唯一差別的只在於我們需要將 type 指定為 PUT 方法。

jQuery 部分:


        function Update() {
            var jsonData = JSON.stringify({ Id: $("#proudctId").val(), Name: $("#prodcutName").val(), Ename: $("#Ename").val(), Color: $("#Color").val() });
            $.ajax({
                url: "/api/Product/" + $("#proudctId").val(),
                type: "PUT",
                contentType: "application/json; charset=utf-8",
                data: jsonData,
                statusCode: {
                    200: function (data) {
                        FindAll();
                    }

                }
            }).fail(function (xhr, Status, errMsg) {
                alert(xhr.statusText);
            });
        }

Web API 部分:


        public HttpResponseMessage PutProduct(int id, [FromBody]Product product)
        {
            if (!ModelState.IsValid)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }

            if (id != product.Id)
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }

            db.Entry(product).State = EntityState.Modified;

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
            }

            return Request.CreateResponse(HttpStatusCode.OK);
        }

Delete - 刪除資料

刪除的方法也算是很簡單,跟 Get 差不多,只需要將 type 屬性指定為 DELETE 來告訴 Web API 就可以了。

jQuery 部分:


        function Delete() {
            $.ajax({
                url: "/api/Product/" + $("#proudctId").val(),
                type: "DELETE",
                contentType: "application/json; charset=utf-8",
                statusCode: {
                    200: function (data) {
                        FindAll();
                    }
                }
            }).fail(function (xhr, Status, errMsg) {
                alert(xhr.statusText);
            });
        }

Web API 部分:


        public HttpResponseMessage DeleteProduct(int id)
        {
            Product product = db.Product.Find(id);
            if (product == null)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }

            db.Product.Remove(product);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
            }

            return Request.CreateResponse(HttpStatusCode.OK, product);
        }

完成程式碼:


        $(function () {
            FindAll();
            $("#get").click(function () {
                Find();
            });

            $("#post").click(function () {
                Creat();
            });

            $("#update").click(function () {
                Update();
            });

            $("#delete").click(function () {
                Delete();
            });
        });
        function FindAll() {
            $.getJSON("/api/Product", function (data) {
                var tr = "";
                for (var i = 0; i <= data.length - 1; i++) {
                    tr += "<tr>" +
                        "<td>" + data[i].Id + "<td>" +
                        "<td>" + data[i].Name + "<td>" +
                        "<td>" + data[i].Ename + "</td>" +
                        "<td>" + data[i].Color + "</td></tr>";
                }
                $("#dataList").html(tr);
            });
        }

        function Find() {
            var id = $("#proudctId").val();
            if (id.length == 0) return false;

            $.getJSON("/api/Product/" + id, function (data) {
                $("#dataList").empty();
                $("#dataList").append("<tr>" +
                        "<td>" + data.Id + "</td>" +
                        "<td>" + data.Name + "</td>" +
                        "<td>" + data.Ename + "</td>" +
                        "<td>" + data.Color + "</td></tr>");

            }).fail(function (xhr, Status, errMsg) {
                alert(xhr.statusText);
            });
        }

        function Creat() {
            var jsonData = JSON.stringify({ Name: $("#prodcutName").val(), Ename: $("#Ename").val(), Color: $("#Color").val() });
            $.ajax({
                url: "/api/Product/",
                type: "POST",
                contentType: "application/json; charset=utf-8",
                data: jsonData,
                statusCode: {
                    201: function (data) {
                        FindAll();
                    }
                }
            });
        }

        function Update() {
            var jsonData = JSON.stringify({ Id: $("#proudctId").val(), Name: $("#prodcutName").val(), Ename: $("#Ename").val(), Color: $("#Color").val() });
            $.ajax({
                url: "/api/Product/" + $("#proudctId").val(),
                type: "PUT",
                contentType: "application/json; charset=utf-8",
                data: jsonData,
                statusCode: {
                    200: function (data) {
                        FindAll();
                    }

                }
            }).fail(function (xhr, Status, errMsg) {
                alert(xhr.statusText);
            });
        }

        function Delete() {
            $.ajax({
                url: "/api/Product/" + $("#proudctId").val(),
                type: "DELETE",
                contentType: "application/json; charset=utf-8",
                statusCode: {
                    200: function (data) {
                        FindAll();
                    }
                }
            }).fail(function (xhr, Status, errMsg) {
                alert(xhr.statusText);
            });
        }

總結

透過 jQuery 來操作 Web API 非常方便,比較需要注意的應該就是 request 的方法,記得 CRUD 要對應到相符合的 HTTP 動詞,在操作上也只要了解這點也能很容易的透過 Web API 來進行許多操作,另外值得一提的是 [FromBody] 與 [FromUri] 這兩者之間的差異需要搞清楚,而預設也是 [FromUri] 如果一不小心用錯了很可能造成程式有 Bug ,而 Web API 本身也非常輕,很適合我們來做這些動作,並且搭配 jQuery 很容易就可以做到像是 Ajax 或是 SPA 的這種效果。


 

新手發文,如有錯誤煩請告知,感謝。
如果喜歡我的文章請按推薦,有任何問題歡迎下面留言~~~

 

 

簽名:

學習這條路很廣,喜歡什麼技術不重要,重要的是你肯花時間去學習