掌握一些web api的開發觀念,快速入門
前言
雖然筆者寫web api寫了很多年了,但是我發覺寫.net的人其實非常多都還沒開始學習web api,所以興起念頭想寫一篇入門教學,還有觀念上的講解,那就快速開始吧。
導覽
- 何謂Restful Api
- 開始著手寫web api的crud吧
- 使用router attribute來快速定義路由
- 關於各動詞的正確觀念
- 使用json來方便呼叫Get的方式
- 請至少遵守HttpStateCode的規則,接著才定義一些自己的規則吧
- 結論
何謂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成自定義的類別,所以你定義的強型別對接收端來說一點幫助都沒有啊,以上如果有更好的想法或建議,再請多多給予指教。