2014年5月5日 星期一

ASP .NET MVC4 WebApi -- OData 使用 與 實作$inlinecount

WebApi提供各種平台取得相關資料,為了滿足各種平台不同的需求(排序、分頁、查詢等等),最直覺的方式就是指定參數給他們使用,再處理各個參數,但這樣就變成不夠彈性,不需要查詢也要傳參數的值,同時加重了前、後端開發人員的麻煩。

使用OData讓前端傳入參數,不需要寫死程式,隨心所欲的完成排序、分頁、查詢等功能。
先來看看OData常用參數:
$top傳回前幾筆資料
$skip跳過幾筆資料
$filter
  • 查詢(where)
    eq-等於、gt-大於、lt-小於、ne-不等於
  • 串連
    and、or
  • $orderby排序
    $inlinecount傳回資料、總筆數、下一頁Url
  • allpages
  • none(預設)
  • 使用方式跟一般傳參數一樣,例如:http://{domain}/api/{controller}/{action}?$top=5&$skip=10
    (更多參數說明請參考官方網站)

    在API的部分,需要設定屬性[Queryable],就可以使用OData。
    備註:
    1.許多文章都說需要使用[Queryable]搭配回傳IQuerable、AsQueryable(),經過測試後,回傳IEnumerable也是可以正常使用的。
    2.如果不想開放所有參數、或有一些限制條件,後端人員可以在[Queryable]設定相關參數以達到目的

    這樣就可以依照各種情況由前端開發人員自行決定需要傳遞那些參數,而後端開發人員只需要專心的處理資料以確保資料正確性即可。


    在測試的時候發現$inlinecount一直沒辦法使用(微軟好像不支援一些參數),這樣前端在做分頁時就不知道資料總筆數,所以我們動手實作一個屬性吧

    public class InlineCountQueryableAttribute : QueryableAttribute
        {
            private static MethodInfo _createPageResult =
                typeof(InlineCountQueryableAttribute)
                .GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
                .Single(m => m.Name == "CreatePageResult");
    
            public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
            {
                base.OnActionExecuted(actionExecutedContext);
    
                HttpRequestMessage request = actionExecutedContext.Request;
                HttpResponseMessage response = actionExecutedContext.Response;
    
                IQueryable result;
                if (response.IsSuccessStatusCode
                    && response.TryGetContentValue<IQueryable>(out result))
                {
                    long? inlineCount = request.GetInlineCount();
                    if (inlineCount != null)
                    {
                        actionExecutedContext.Response = _createPageResult.MakeGenericMethod(result.ElementType).Invoke(
                            null, new object[] { request, request.GetInlineCount(), request.GetNextPageLink(), result }) as HttpResponseMessage;
                    }
                }
            }
    
            internal static HttpResponseMessage CreatePageResult<T>(HttpRequestMessage request, long? count, Uri nextpageLink, IEnumerable<T> results)
            {
                return request.CreateResponse(HttpStatusCode.OK, new PageResult<T>(results, nextpageLink, count));
            }
        }
    
    設定屬性由[Queryable]改為[InlineCountQueryable]即可正常使用$inlinecount參數

    參考:
    System.Web.Http.OData 命名空間
    OData官網
    關於IQueryable特性的小實驗
    Web API, OData, $inlinecount and testing


    2014/07/22 KaiYai補充:
    最近無意間發現了Bug,當程式碼出現錯誤產生Exception,Action回前端時依然會進到Attribute內執行OnActionExecuted事件,因為是Exception,所以傳進來的HttpActionExecutedContext.Response會是null,而HttpActionExecutedContext.Exception會是錯誤資訊,如果利用原本程式碼收到的錯誤訊息不會是原本實際發生錯誤的部分,而是會出現OnActionExecuted內的錯誤:
    <Error>
        <Message>發生錯誤。</Message>
        <ExceptionMessage>並未將物件參考設定為物件的執行個體。</ExceptionMessage>
        <ExceptionType>System.NullReferenceException</ExceptionType>
        <StackTrace>
            ...略...
        </StackTrace>
    </Error>
    

    但實際上的錯誤應該是
    <Error>
        <Message>發生錯誤。</Message>
        <ExceptionMessage>輸入字串格式不正確。</ExceptionMessage>
        <ExceptionType>System.FormatException</ExceptionType>
        <StackTrace>
            ...略...
        </StackTrace>
    </Error>
    

    這是因為response已經收到null值,指令沒有判斷到是否為null,就會發生會設定物件的錯誤。
    所以修正條件式加上判斷response != null
    if (response != null && response.IsSuccessStatusCode && response.TryGetContentValue<IQueryable>(out result))

    原本想要直接判斷HttpActionExecutedContext.Exception != null,但想了想,為什麼執行base.OnActionExecuted(actionExecutedContext);沒有發生錯誤呢?所以決定看一下QueryableAttribute的Code,看到下的條件以後,毅然決然的直接照辦,有錯微軟會先被罵
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        ...略...
        HttpResponseMessage response = actionExecutedContext.Response;
    
        if (response != null && response.IsSuccessStatusCode) 
        ...略...
    }
    


    參考:
    QueryableAttribute Source(aspnetwebstack /src/System.Web.Http.OData/QueryableAttribute.cs)

    5 則留言 :

    1. 您好,我使用了你這個方法後,$skip會發生錯誤,$skip=1 會跳過2筆,$skip=2會跳過4筆~~以此類推~~

      回覆刪除
      回覆
      1. 簡sir 您好:
        測試後並沒有發生如您所說的錯誤,是否可以貼上Code協助您測試找出原因?
        如果不方便公開貼上來,也可以透過email方式提供(mmaarrkk02@hotmail.com、mmaarrkk02@gmail.com)

        刪除
      2. 不好意思。剛剛遠端回公司電腦,把整個odata套件移除後重新安裝,再重新依照您的步驟在設定一次,就OK了,我想可能是我在某個環節出了狀況沒注意到。
        很抱歉浪費您的時間幫我測試~~~非常感謝。

        刪除
      3. 下午處理這個用了一下午,我想我應該加強我檢查錯誤的細心程度了。

        刪除
      4. 簡sir 您好:
        能幫助到您是我們的榮幸,如有任何疑問或更好的建議都歡迎提出,讓眾多辛苦的IT人員一起學習成長^^

        刪除

    Related Posts Plugin for WordPress, Blogger...