剛才無意中看到《什麼是REST?》一文。文章雖然很短,短到我幾乎要鄙夷一下作者的程度,但是仔細看了下,確也發現本文著實有用。
作為一名想盡量實現純RESTful服務的人(或可稱為RESTful原教旨主義者)來說,希望做出來的服務能盡量的符合RESTful原則定義,如果做出來一個RPC + ROA(面向資源的架構,其定義見《RESTful Web Services 中文版》)的服務,那還不如直接使用WCF算了,雖然,羅馬不是一天建成的,我也不可能一上手就構建一個純RESTful服務,但是盡量向標準靠攏還是很有必要的。
文中提到了一些概念,我很快發現在我的實踐中有兩條不符合。
其一:“REST式的Web Service使用HTTP裡的方法:GET, POST, DELETE, PUT。你不需要使用URL或請求的內容來指定這個方法。”
而在我目前的做法中,我是使用了類似這樣的Url:http://www.cleversoft.com/svc/log/create作為建立一條日誌記錄的url的,很明顯,在這個Url中,create是不必要的,甚至也是不應該出現的,為什麼呢,RESTful的原則之一:統一介面原則,為啥具有這個原則呢,統一介面的目的是建立一種面向程式的網站,既然是面向程式的,大家都嚴格的尊重約定便是一個十分重要的事情了,否則你開放了一個資源,如:http://www.anywhere.com/blog,(實際上這個資源甚至可以稱為API了)別人怎麼知道後面是Get還是Search才能調用呢?但是運用了統一介面原則,大家很自然就想到使用GET(Http Method)來擷取資料了,這就在很大程度上簡化了MetaData的定義。回到我的應用程式中來說,對於Log服務來說,http://www.cleversoft.com/svc/log/已經很明確的代碼了Log服務(資源的表示),使用POST方法就代表是建立日誌記錄了,畫蛇添足的加上一個Create,不是又回到RPC方式了嗎?
按照ASP.NET MVC的Route規則來說,想要用戶端使用http://www.cleversoft.com/svc/log的方式來調用到一個POST的方法,最簡單直接的方法便是建立一個方法
[HttpPost]public ActionResult Index(){ //...}
但是..這似乎也太銼了點吧,明明一個Create方法,使用Index..用戶端爽了,伺服器端的人該罵娘了吧,還是繼續使用Create方法名稱為好,代碼可讀性也是至上的啊。那就只能從Url Routing上想辦法了,結果很快發現了一個類型HttpMethodConstraint,哈哈,看起來就是為這件事準備的吧,於是為Url Routing添加新執行個體,如下代碼所示:
public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute("Svc_default_Create", "Svc/{controller}/{action}", new { action = "Create" }, new { httpMethod = new HttpMethodConstraint("POST") } ); context.MapRoute("Svc_default_Update", "Svc/{controller}/{action}", new { action = "Update" }, new { httpMethod = new HttpMethodConstraint("PUT") } ); context.MapRoute("Svc_default_Delete", "Svc/{controller}/{action}", new { action = "Delete" }, new { httpMethod = new HttpMethodConstraint("Delete") } ); context.MapRoute( "Svc_default", "Svc/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional } ); }
將用戶端Url中的Create刪除,使用http://www.cleversoft.com/svc/log添加Log記錄,成功!
下面說說其二,第2個相對來說更簡單點。文中提到“REST式的Web Service使用HTTP狀態代碼作為傳回值。”,我看Google中的服務,很多都是返回被建立(修改)的實體的,這我倒不願意,我向伺服器發了什麼我還不知道麼,這一來一回的多浪費啊,所以我就返回了new EmptyResult(),當然,返回EmptyResult應該就是返回一個200(Http StatusCode)吧,但是對於Create,Update,Delete來說,都返回200似乎顯的不那麼專業,於是對之前我做的HttpStatusResult稍作改動,為Controller添加幾個擴充方法:
public static HttpStatusResult GetResult200(this Controller controller, params string[] content) { return new HttpStatusResult(HttpStatusCode.OK, content.Length > 0 ? content[0] : "OK"); } public static HttpStatusResult GetResult201(this Controller controller, params string[] content) { return new HttpStatusResult(HttpStatusCode.Created, content.Length > 0 ? content[0] : "Created"); } public static HttpStatusResult GetResult202(this Controller controller, params string[] content) { return new HttpStatusResult(HttpStatusCode.Accepted, content.Length > 0 ? content[0] : "Accepted"); } public static HttpStatusResult GetResult204(this Controller controller, params string[] content) { return new HttpStatusResult(HttpStatusCode.NoContent, content.Length > 0 ? content[0] : "No Content"); }
這樣,就可以視情況向用戶端如實反映操作結果了,萬一以後有人使用別的用戶端調我的服務,也不至於罵我NC了,呵呵
使用方法樣本:
[HttpPost] [ServiceError] public ActionResult Create([AtomEntryParameterConvert(typeof(LogEntry))]LogRecord log) { var logMng = new LogManager(); logMng.CreateLog(log); return this.GetResult201(); }
ps: 聽說.NET 5.0可能會有擴充屬性?如果真有的話就不會讓這個操作顯的那麼難看了,期待...