【範例】呼叫Self-Hosted ASP.NET Web API
| | | 3 | |
前一篇文章提到不靠IIS在Console/WinForm/WPF程式裡也可以執行ASP.NET Web API,接著我們更深入一點,談談Client端如何傳遞資料給ASP.NET Web API。
在ASP.NET Web API的傳統應用,Client端多是網頁,故常見範例是透過HTML Form、JavaScript、AJAX傳送參數資料給Web API;而在Self-Hosted ASP.NET Web API情境,由於Web API常被用於系統整合,呼叫端五花八門,.NET程式、VBScript、Excel VBA、Java... 都有可能,所幸Web API建構在HTTP協定之上,不管平台為何,都不難找到可用的HTTP Client元件或函式庫。
本文將示範我自己常用的兩種平台: .NET Client及Excel VBA。
首先,我們改寫前文範例,加上接受前端傳入Player物件新增資料的Insert() Action。由於ASP.NET MVC的ModelBinder已具備將JSON字串轉為Model物件的能力,我們也沒什麼好客氣的,直接宣告Player物件當成Insert()的輸入參數,JSON字串轉物件的工作就丟給ASP.NET MVC底層傷腦筋。
BlahController.cs改寫如下,程式碼很單純,唯一的小手腳是要捕捉例外,產生自訂錯誤訊息的HttpResponseMessage,再以其為基礎拋出HttpResponseException,理由容後說明。
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Web.Http; using Newtonsoft.Json; namespace SelfHostWebApi { public class BlahController : ApiController
{ //宣告Model類別承接前端傳入資料 public class Player
{ public int Id;
public string Name;
public DateTime RegDate; public int Score;
}
[HttpPost]
public string Insert(Player player)
{ try { //輸出資料,驗證已正確收到 Console.WriteLine("Id: {0:0000} Name: {1}", player.Id, player.Name); Console.WriteLine("RegDate: {0:yyyy-MM-dd} Score: {1:N0}", player.RegDate, player.Score);
return "Player [" + player.Id + "] Received";
}
catch (Exception ex) { //發生錯誤時,傳回HTTP 500及錯誤訊息 var resp = new HttpResponseMessage() { StatusCode = HttpStatusCode.InternalServerError,
Content = new StringContent(ex.Message), ReasonPhrase = "Web API Error" };
throw new HttpResponseException(resp);
}
}
}
}
呼叫端的寫法很簡單,WebClient.UploadString(url, jsonString)會以jsonString為內容丟出HTTP POST請求,但有個關鍵: 必須設定ContentType為application/json,告知ModelBinder我們所POST的內容是JSON字串,ModelBinder才能正確地反序列化成Player類別。測試程式另外亂傳空字串及非JSON字串,以測試輸入錯誤時Web API的反應。
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using Newtonsoft.Json; namespace ApiTest { class Program { static void Main(string[] args)
{ WebClient wc = new WebClient(); string url = "httq://localhost:32767/blah/Insert";
//由WebException中取出Response內容 Action<string> postJson = (json) => { try { //重要: 需宣告application/json,才可正確Bind到Model wc.Headers.Add(HttpRequestHeader.ContentType,
"application/json"); var test = wc.UploadString(url, json);
Console.WriteLine("Succ: " +test); }
catch (WebException ex) { StreamReader sr = new StreamReader( ex.Response.GetResponseStream());
Console.WriteLine("Error: " + sr.ReadToEnd()); }
};
//故意傳入無效資料進行測試 postJson(string.Empty); postJson("BAD THING"); //利用匿名型別+Json.NET傳入Web API所需的Json格式 var player = new { Id = 1,
Name = "Jeffrey", RegDate = DateTime.Today,
Score = 32767
};
postJson(JsonConvert.SerializeObject(player));
Console.ReadLine();
}
}
}
測試結果如下:
Error: Object reference not set to an instance of an object. Error: Object reference not set to an instance of an object. Succ: "Player [1] Received"
當傳入空字串及非JSON字串,UpdateString()會發生WebException,而透過WebException.Response.GetResponseStream()可讀取Insert()方法在捕捉例外時透過HttpResponseMessage傳回的訊息內容。如果我們不捕捉例外,任由MVC內建機制處理,則Client會收到Exception經JSON序列化後的結果(如下所示),資訊較詳細但需反序列化才能讀取。相形之下,拋回HttpResponseException可以精準地控制傳回的錯誤訊息及提示,更容易符合專案客製需求。
Error: {"Message":"An error has occurred.","ExceptionMessage":"Object reference
not set to an instance of an object.","ExceptionType":"System.NullReferenceExcep
tion","StackTrace":" at SelfHostWebApi.BlahController.InsertByBinding(Player p
layer) in x:\\Temp\\Lab0603\\SelfHostWebApi\\SelfHostWebApi\\BlahController.cs:l
ine 34\r\n at lambda_method(Closure , Object , Object[] )\r\n at System.Web.
Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass1
3.<GetExecutor>b__c(Object instance, Object[] methodParameters)\r\n at System.
Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object
instance, Object[] arguments)\r\n at System.Threading.Tasks.TaskHelpers.RunSy
nchronously[TResult](Func`1 func, CancellationToken cancellationToken)"}
最後補上VBA寫法:
Sub SendApiRequest(body As String)
Dim xhr Set xhr = CreateObject("MSXML2.ServerXMLHTTP")
Dim url As String
url = "httq://localhost:32767/blah/insert" xhr.Open "POST", url, False
xhr.SetRequestHeader "Content-Type", "application/json"
On Error GoTo HttpError:
xhr.Send body
MsgBox xhr.responseText
Exit Sub
HttpError:
MsgBox "Error: " & Err.Description End Sub
Sub Test() SendApiRequest "BAD THING" SendApiRequest "{ ""Id"":1,""Name"":""Jeffrey"", " & _
"""RegDate"":""2012-12-21"", ""Score"":32767 }"
End Sub
Comments
# by wpf
Thank you a lot.
# by wpf
你好,Client 端 string url = "httq://localhost:32767/blah/Insert"; 有打錯,應該是 http。
# by Jeffrey
to wpf, 寫成 httq 是為了避免無效網址被系統當成有效連結。