ASP.NET MVC - Web API OData Batching 406 Not Acceptable 錯誤
這篇稍微簡單的紀錄一下,浪費掉我打電動認真陪家人的兩個放假天,那話也說在前面,根據查到的狀況,看起來,應該是Web API的BUG,但官方並沒有承諾甚麼時候會改好,只能誠心祈禱RTM的Web API 2能順利修正,所以,未來如果有遇到類似的問題,大家可以試著升級成最新版本看看。
最後,和這件事以外的另外一個補充,先不論這件事情,目前Web API OData的支援程度還是有很多地方沒完整實現,所以很容易踩到地雷,當然,官方還是在努力地將Web API越做越好;而如果害怕擔心,對於WCF又已經熟到不行的朋友,那可以考慮直接用WCF時做OData Endpoint吧=v=。
那這個問題是甚麼??..
主要是小弟在整合OData方案的時候,搭配JayData和Datajs來協助對OData Service來進行資料的管控與存取,一般的使用上,都沒遇到甚麼多大的問題 ( 也吃了不少苦….因為現在文件幾乎都是針對WCF… ),但最近在處理以下Code的時候,缺發生了一個比較棘手的問題;喔,對了,看不太懂此Code沒關係,簡單的說,我就是要利用JS來新增資料,先用add去新增到Context,然後再用JayData的Framework來saveChanges方法,一次更新。
var CustomerAgreementType =
new InternalIMS.Model.CustomerAgreementType({ CustomerID: fkID });
CustomerAgreementType.ModifyDate = Date.now();
mydatabase.Agreement.add(model);
mydatabase.CustomerAgreementType.add(CustomerAgreementType);
mydatabase.saveChanges(function () {
});
上面那個Code看不懂其實沒關係,總之,我想要的效果,就是利用一次的Request,來送出兩個新增的訊息( 以前的話,要送出兩次Request來達成新增。)
而之前的Web API連此功能都沒有…Orz..在Web API 2也已經提供了此功能,此功能就稱為Batching,有興趣的朋友,可以看這篇架設。(其實也只是多設定一行而已…)
但真正的問題在後面,我們先來看一下,要新增兩筆資料的Requst,如下圖,我們可以看到一次發送兩筆POST ( 因為我們要新增嘛… )
但當我們看Response的時候,我們可以看到發生了406 Not Acceptable錯誤。
當下發生這個錯誤,真的是把我搞死…後來才查到,原來是OData服務網址的錯誤,也就是這張圖的這個地方。
是的,其實是URL錯誤,上圖的URL的位置其實是http://localhost/Agreement和http://localhost/CustomerAgreementType,但實際上,我們的位置卻應該是在localhost/OData/Agreement之下…所以會返回406錯誤。
為了驗證真的是這個問題,所以小弟我直接用datajs來進行測試,Code如下。
OData.request({
requestUri: "/OData/$batch",
method: "POST",
data: {
__batchRequests: [
{
__changeRequests: [
{ requestUri: "/OData/Agreement", method: "POST", data: model }
]
},
{ requestUri: "/OData/Agreement", method: "GET" }
]
}
}, function (data, response) {
alert("ok");
}, function () {
alert("request failed");
}, OData.batchHandler);
結果就很順利…我們可以看到POST後的網址已經正確了。
而Respose也正常了。
所以到這邊,就可以推測是這裡的問題了。
至於解法,目前小弟我查到的訊息,除了等Web API更新以外,要不就是自己重新定義處理器。
處理器的Code如下。( 此Code來源於JayData論壇的Fenderbender神人 )
public class PathFixODataBatchHandler : DefaultODataBatchHandler
{
public PathFixODataBatchHandler(HttpServer httpServer)
: base(httpServer)
{
}
public override async Task<IList<ODataBatchResponseItem>> ExecuteRequestMessagesAsync(IEnumerable<ODataBatchRequestItem> requests, CancellationToken cancellationToken)
{
if (requests == null)
{
throw new System.ArgumentNullException("requests"); // Error.ArgumentNull("requests");
}
IList<ODataBatchResponseItem> responses = new List<ODataBatchResponseItem>();
try
{
foreach (ODataBatchRequestItem request in requests)
{
fixRequestUri(request);
responses.Add(await request.SendRequestAsync(Invoker, cancellationToken));
}
}
catch
{
foreach (ODataBatchResponseItem response in responses)
{
if (response != null)
{
response.Dispose();
}
}
throw;
}
return responses;
}
private void fixRequestUri(ODataBatchRequestItem request)
{
foreach (HttpRequestMessage req in ((ChangeSetRequestItem)request).Requests)
{
var oldUri = req.RequestUri;
var newUriBuilder = new UriBuilder(oldUri);
newUriBuilder.Path = "/odata" + newUriBuilder.Path;
req.RequestUri = newUriBuilder.Uri;
}
}
}
其實重點就是後面,將path加上"/odata"。
然後我們在Web API的Router Config再來調整一下,改用上面的Class。
config.Routes.MapODataRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: GetModel(),
//batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
batchHandler: new PathFixODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnableQuerySupport();
這樣就可以了~~
後記
目前查出來的文章,都指向ASP.NET WEB API的問題XDD,因為除了JayData外,BreezeJS也有同樣的問題;但WCF下,就無此問題;但不管怎樣,有用到此技術的朋友們,可以稍微注意一下喔!!
參考網址
- http://www.odata.org/documentation/odata-v2-documentation/batch-processing/
- http://stackoverflow.com/questions/18813890/post-batch-request-with-breezejs
- http://jaydata.org/forum/viewtopic.php?f=3&t=300
- https://github.com/jaydata/jaydata/issues/131
- http://aspnetwebstack.codeplex.com/wikipage?title=Web%20API%20Request%20Batching&referringTitle=Specs