[WEB API]掌握一些開發觀念,快速入門

掌握一些web api的開發觀念,快速入門

前言

雖然筆者寫web api寫了很多年了,但是我發覺寫.net的人其實非常多都還沒開始學習web api,所以興起念頭想寫一篇入門教學,還有觀念上的講解,那就快速開始吧。

導覽

  1. 何謂Restful Api
  2. 開始著手寫web api的crud吧
  3. 使用router attribute來快速定義路由
  4. 關於各動詞的正確觀念
  5. 使用json來方便呼叫Get的方式
  6. 請至少遵守HttpStateCode的規則,接著才定義一些自己的規則吧
  7. 結論

 

何謂Restful api

其實Restful api也就是要對應了create(post),read(get),update(put),delete(delete)的動作,這些動詞在web api都有對應,如果我們新增了一個web api的新專案,預設在controller應該會看到ValuesContoller,但其實這個預設的真的不是一個很好的示範,因為呼叫web api得到的並不是一個我們定義的型別,就像mvc裡面使用ActionResult一樣,在web api請愛用IHttpActionResult,再者雖然Restful api是一個標準規範,遺憾的是通常一個api各式各樣的取資料會多過其餘的非常多,所以如果我們要真正遵照Restful api的規則,我們可能會有很多類似的web api都是在取資料的,所以有時候同類型的集中在一個web api或許會是比較好的方式哦。

 

開始著手寫web api的crud吧

以ValuesController這支預設的來看,我會先把這支全部改成IHttpActionResult的方式

using System.Web.Http;

namespace WebApiTest.Controllers
{
    public class ValuesController : ApiController
    {
        // GET api/values
        public IHttpActionResult Get()
        {
            return Ok(new string[] { "value1", "value2" });
        }

        // GET api/values/5
        public IHttpActionResult Get(int id)
        {
            return Ok("value");
        }

        // 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)
        {
        }
    }
}

 

先定義一個簡單的employee的類別吧,之後用此類別來做crud

public class Employee
    {
        public int EmployeeId { get; set; }
        public string Name { get; set; }
    }
public class EmployeeDao
    {
        public static List<Employee> Employees { get; set; } = new List<Employee>
        {
            new Employee {EmployeeId=1,Name="Anson",Address="台北",PhoneNumber="0911" },
            new Employee {EmployeeId=2,Name="Jason",Address="高雄",PhoneNumber="0922" },
            new Employee {EmployeeId=3,Name="Andy",Address="新竹",PhoneNumber="0910" },
        };
    }

curd的部份說明一下,我們所有的方法名稱就是動詞名稱,所以就不用多設定attribute了

using System.Linq;
using System.Web.Http;
using WebApiTest.Models;

namespace WebApiTest.Controllers
{
    public class ValuesController : ApiController
    {
        
        public IHttpActionResult Get()
        {
            return Ok(EmployeeDao.Employees);
        }

        // GET api/values/5
        public IHttpActionResult Get(int id)
        {
            return Ok(EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == id));
        }

        // POST api/values
        public IHttpActionResult Post(Employee employee )
        {
            EmployeeDao.Employees.Add(employee);
            return Ok(EmployeeDao.Employees);
        }

        // PUT api/values/5
        public IHttpActionResult Put(Employee employee)
        {
            var result=EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == employee.EmployeeId);
            if (result != null)
            {
                result.Name = employee.Name;
                result.PhoneNumber = employee.PhoneNumber;
                result.Address = employee.Address;
            }
            return Ok(EmployeeDao.Employees);
        }

        // DELETE api/values/5
        public IHttpActionResult Delete(int id)
        {
            EmployeeDao.Employees.RemoveAll(x=>x.EmployeeId==id);
            return Ok(EmployeeDao.Employees);
        }
    }
}

 

使用router attribute來快速定義路由

承如我之前說的,現實狀況是我們光是取資料可能就有很多種,新刪修可能只有一種,那依照restful api的規則,我們就要多建立很多web api來取資料,所以實務上我會使用attribute的方式來取資料,而且我也比較喜歡使用attribute的方式來設定GET或POST,在底下的範例多新增了一個search的名稱,來代表要搜尋的get方式

[RoutePrefix("/Api/Values")]
    public class ValuesController : ApiController
    {
        [Route("Get")]
        [HttpGet]
        public IHttpActionResult Get()
        {
            return Ok(EmployeeDao.Employees);
        }

        [Route("GetById")]
        [HttpGet]
        public IHttpActionResult GetById(int id)
        {
            return Ok(EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == id));
        }

        [Route("Search")]
        [HttpGet]
        public IHttpActionResult Search(string name)
        {
            return Ok(EmployeeDao.Employees.FirstOrDefault(x => x.Name.Contains(name)));
        }

        [Route("Post")]
        [HttpPost]
        public IHttpActionResult Post(Employee employee )
        {
            EmployeeDao.Employees.Add(employee);
            return Ok(EmployeeDao.Employees);
        }

        [Route("Put")]
        [HttpPut]
        public IHttpActionResult Put(Employee employee)
        {
            var result=EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == employee.EmployeeId);
            if (result != null)
            {
                result.Name = employee.Name;
                result.PhoneNumber = employee.PhoneNumber;
                result.Address = employee.Address;
            }
            return Ok(EmployeeDao.Employees);
        }

        [Route("Delete")]
        [HttpDelete]
        public IHttpActionResult Delete(int id)
        {
            EmployeeDao.Employees.RemoveAll(x=>x.EmployeeId==id);
            return Ok(EmployeeDao.Employees);
        }
    }

如果我們確定完全都不要使用web api預設的resful的方式,我們也是可以直接改掉WebApiConfig.cs的路由設定

config.Routes.MapHttpRoute(                         
    name: "DefaultApi",                             
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new { id = RouteParameter.Optional }  
);                                                  

接著我們的controller就可以改成如下了

    public class ValuesController : ApiController
    {
        public IHttpActionResult Get()
        {
            return Ok(EmployeeDao.Employees);
        }

        public IHttpActionResult GetById(int id)
        {
            return Ok(EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == id));
        }

        public IHttpActionResult Search(string name)
        {
            return Ok(EmployeeDao.Employees.FirstOrDefault(x => x.Name.Contains(name)));
        }

        [HttpPost]
        public IHttpActionResult Post(Employee employee)
        {
            EmployeeDao.Employees.Add(employee);
            return Ok(EmployeeDao.Employees);
        }

        [HttpPut]
        public IHttpActionResult Put(Employee employee)
        {
            var result = EmployeeDao.Employees.FirstOrDefault(x => x.EmployeeId == employee.EmployeeId);
            if (result != null)
            {
                result.Name = employee.Name;
                result.PhoneNumber = employee.PhoneNumber;
                result.Address = employee.Address;
            }
            return Ok(EmployeeDao.Employees);
        }

        [HttpDelete]
        public IHttpActionResult Delete(int id)
        {
            EmployeeDao.Employees.RemoveAll(x => x.EmployeeId == id);
            return Ok(EmployeeDao.Employees);
        }
    }

 

關於各動詞的正確觀念

其實這是筆者自己看過各式各樣寫web api之後的一些想法,我們最好遵守著post and put永遠都是用物件在傳遞,我常看到有人用了post結果是用url?id=1&name=anson,那為何不直接用get就好了,還要用post呢?還有人是傳了一個物件,然後又附帶一個url?token=213123,那為何不把token直接包含進物件裡面呢?而且如果token要做驗證的話,token也應該要放在header的Authorization裡面才比較符合規範,所以請遵守get和delete就是用querystring,post和put都是用json傳輸的方式,這樣子才不會讓人家很難理解你開出來的api很奇怪。

 

使用json來方便呼叫Get的方式

其實這個使用json來呼叫Get的說法不是很正確,應該說是如果我們有特殊情況必須使用get,但是我們想包成一個json,而不是在那邊組querystring的話,就可以使用這種的方式,實際上我們只是假裝包成json,但其實request一樣還是用querystring的方式,那應該怎麼做呢?下面是一段angular傳json去訪問get的示例

那web api其實我們只要宣告[FromUri]就可以了

可以從chrome的開發者工具看出來,雖然我們傳的是json,但實際上已經自動轉成了querystring的方式,再經由web api的設定,就可以順利方便的用json去傳get的方式

 

請至少遵守HttpStateCode的規則,接著才定義一些自己的規則吧

其實http都有標準的標頭,比如從上面的圖示可以看出來是回傳200,401則是代表驗證沒過,500則代表嚴重的錯誤,各個Http state code都有自己的意思,但因為我工作的經驗跟很多公司介接過web api,幾乎每家都不走標準,有些是自己定義代碼配合自己的規格書出給人家,有些則是對應了http state code的代碼,但是自己包個物件,只要遇到這種對接的,心裡只有00XX,重點是他們的規則還常常在變,所以這就是我上面說的請愛用IHttpActionResult來按照你的狀況給出正確的State code,不要自己定義類別,然後又包了一些奇怪的規則,來請人家看完規格書才知道你的規則代表什麼意思,下面來示範一下,如果在web api怎麼回應正確的state code呢?其實web api已經有給我們一些常用的了,我們可以直接使用就好了。

[Route("Message")]
        [HttpGet]
        public IHttpActionResult Message()
        {
            return Ok();
            return BadRequest();
            return Authorization();
        }

但是Http state code明明就很多種,web api好像沒提供那麼多種,我們可以回傳一個HttpResponseMessage的物件,然後裡面包著httpstatecode來定義

[Route("Message")]
        [HttpGet]
        public IHttpActionResult Message()
        {
            var response = new HttpResponseMessage(HttpStatusCode.NoContent);
            return ResponseMessage(response);
        }

另外一種方式則是如下的方式,可以回傳stateCode還有要回傳的訊息或物件,方便直接使用

public IHttpActionResult Get()                            
{                                                         
    Company company = new Company();                      
    return Content(HttpStatusCode.BadRequest, company);   
}                                                         

不過其實直接使用也還有很多種方式,就不一一列舉留給讀者自行研究了,而HttpStateCode實際上是一個enum,包裝了很多的回應碼

可以看到HttpResponseMessage給了我們更大的空間,包含要塞任何訊息或Header之類的。

 

結論

這篇是想要真正的說一下web api比較正確應該怎麼來開發,因為我發覺網路上很多web api的文章,甚少提到應該怎麼正確遵照比較官方的規則來開發,所以web api一些奇奇怪怪的方式開發的都有,就算你定義了回傳強型別,接收端一樣都是弱型別,在前端可以用typescript來自動map欄位,而C#也有方法可以自動map成自定義的類別,所以你定義的強型別對接收端來說一點幫助都沒有啊,以上如果有更好的想法或建議,再請多多給予指教。