2011-10-14 52 views
7

我創建一個REST API,我一直在玩允許來自客戶端的請求的捆綁銷售的想法。通過捆綁銷售,我的意思是他們可以發送一個包含多個「真實」請求的請求,並將它們一起交付給客戶。通常是javascript ajax請求。事情是這樣的:捆綁API請求

POST /bundlerequest 

["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10&page=1", "/products", "/product/categories" ] 

(捆綁的要求只能是GET請求,截至目前至少) 這是爲了恢復這樣的事情

{ 
    "success" : ["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10&page=1", "/products", "/product/categories" ], 
    "error" : [], 
    "completiontime" : 94, 
    other relevant metadata... 
    "responses" : [ 
     {"key" : "/person/3243" , "data" : {"name" : "John", ...} }, 
     {"key" : "/person/3243/friends" , "data" : [{"name": "Peter", "commonfriends" : 5, ...}] }, 
     etc... 
    ] 
} 

好處的這種捆綁是它減少了請求的數量,這對於移動設備尤其重要。

所以我的第一個問題是,是我的做法就是一個很好的一個?有沒有人有過這樣的經歷?

AFAIK解決這個問題的常見方法是編寫服務器端代碼來返回組合數據,我相信這是與客戶端相關的。 (例如,twitter用戶流就是這樣做的,它結合了人員信息,最新推文,最新個人消息等)。但是這使得API非常有見地,當客戶端需要更改時,服務器可能需要更改以適應優化。

而第二個問題是如何實現這一點?

我的後端是ASP.NET MVC 3和IIS 7.我應該在應用程序中實現它嗎?有一個bundlerequest動作可以在內部調用請求中指定的其他動作?

難道直接IIS 7中實現?編寫一個透明地截取/ bundlerequest請求的模塊,然後調用所有相應的子請求,使應用程序完全不知道發生的綁定?這也可以讓我以應用程序不可知的方式實現這一點。

+0

這聽起來很酷。 –

+0

所有這些url只會返回JSON嗎?或者有些可能會返回HTML partials和其他東西? –

+0

@DarinDimitrov是JSON而已,但是我看到被記錄的用戶退出或沒有訪問API部分地區的實例,然後可能會被重定向到一個登錄頁面潛在的問題。這些都是我必須解決的問題。 –

回答

3

你可以使用一個asynchronous controller聚集服務器上的這些請求。讓我們首先從定義將由控制器返回視圖模型:

public class BundleRequest 
{ 
    public string[] Urls { get; set; } 
} 

public class BundleResponse 
{ 
    public IList<string> Success { get; set; } 
    public IList<string> Error { get; set; } 
    public IList<Response> Responses { get; set; } 
} 

public class Response 
{ 
    public string Key { get; set; } 
    public object Data { get; set; } 
} 

則控制器:

public class BundleController : AsyncController 
{ 
    public void IndexAsync(BundleRequest request) 
    { 
     AsyncManager.OutstandingOperations.Increment(); 
     var tasks = request.Urls.Select(url => 
     { 
      var r = WebRequest.Create(url); 
      return Task.Factory.FromAsync<WebResponse>(r.BeginGetResponse, r.EndGetResponse, url); 
     }).ToArray(); 

     Task.Factory.ContinueWhenAll(tasks, completedTasks => 
     { 
      var bundleResponse = new BundleResponse 
      { 
       Success = new List<string>(), 
       Error = new List<string>(), 
       Responses = new List<Response>() 
      }; 
      foreach (var task in completedTasks) 
      { 
       var url = task.AsyncState as string; 
       if (task.Exception == null) 
       { 
        using (var response = task.Result) 
        using (var stream = response.GetResponseStream()) 
        using (var reader = new StreamReader(stream)) 
        { 
         bundleResponse.Success.Add(url); 
         bundleResponse.Responses.Add(new Response 
         { 
          Key = url, 
          Data = new JavaScriptSerializer().DeserializeObject(reader.ReadToEnd()) 
         }); 
        } 
       } 
       else 
       { 
        bundleResponse.Error.Add(url); 
       } 
      } 
      AsyncManager.Parameters["response"] = bundleResponse; 
      AsyncManager.OutstandingOperations.Decrement(); 
     }); 
    } 

    public ActionResult IndexCompleted(BundleResponse response) 
    { 
     return Json(response, JsonRequestBehavior.AllowGet); 
    } 
} 

現在我們可以調用它:

var urls = [ 
    '@Url.Action("index", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)', 
    '@Url.Action("friends", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)', 
    '@Url.Action("index", "comments", new { id = 3243, pagesize = 10, page = 1 }, Request.Url.Scheme, Request.Url.Host)', 
    '@Url.Action("index", "products", null, Request.Url.Scheme, Request.Url.Host)', 
    '@Url.Action("categories", "product", null, Request.Url.Scheme, Request.Url.Host)' 
]; 
$.ajax({ 
    url: '@Url.Action("Index", "Bundle")', 
    type: 'POST', 
    contentType: 'application/json; charset=utf-8', 
    data: JSON.stringify(urls), 
    success: function(bundleResponse) { 
     // TODO: do something with the response 
    } 
}); 

當然一些調整可能是必要的,以適應您的具體需求。例如,您提到發送會話過期的AJAX請求,可能會重定向到登錄頁面,從而無法捕獲錯誤。這確實是ASP.NET中的PITA。 Phil Haack blogged一種以RESTful方式規避這種不良行爲的可能方式。您只需要爲請求添加自定義標頭。

+0

感謝您提供非常詳細的答案,並指出我正確解決表單身份驗證問題。看起來就像我需要的東西。我將以我的實現爲基礎,看看它如何適合我的應用。 –