捆绑API请求

时间:2011-10-14 09:15:35

标签: javascript json asp.net-mvc-3 api iis-7

我正在创建一个REST API,我一直在想要允许捆绑来自客户端的请求。通过捆绑我的意思是他们可以发送一个包含多个“真实”请求的请求,并将它们一起发送给客户端。通常是javascript ajax请求。像这样:

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的请求,然后调用所有相应的子请求,使应用程序完全不知道发生的捆绑?这也允许我以与应用程序无关的方式实现它。

2 个答案:

答案 0 :(得分: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方式规避这种不受欢迎的行为。您只需要为请求添加自定义标头。

答案 1 :(得分:0)

我建议查看wcf web api