2011-10-14 49 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

感谢您提供非常详细的答案,并指出我正确解决表单身份验证问题。看起来就像我需要的东西。我将以我的实现为基础,看看它如何适合我的应用。 –