ASP.NET MVC - Web API OData Batching 406 Not Acceptable 錯誤

06 October 2013 — Written by Sky Chang
#ASP.NET MVC#JavaScript#JayData#OData#WCF

這篇稍微簡單的紀錄一下,浪費掉我打電動認真陪家人的兩個放假天,那話也說在前面,根據查到的狀況,看起來,應該是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 ( 因為我們要新增嘛… )

image

但當我們看Response的時候,我們可以看到發生了406 Not Acceptable錯誤。

image

當下發生這個錯誤,真的是把我搞死…後來才查到,原來是OData服務網址的錯誤,也就是這張圖的這個地方。

image

是的,其實是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後的網址已經正確了。

image

而Respose也正常了。

image

所以到這邊,就可以推測是這裡的問題了。

至於解法,目前小弟我查到的訊息,除了等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下,就無此問題;但不管怎樣,有用到此技術的朋友們,可以稍微注意一下喔!!

參考網址

Sky & Study4.TW