Mar*_*sen 8 javascript api iis-7 json asp.net-mvc-3
我正在创建一个REST API,我一直在想要允许捆绑来自客户端的请求.通过捆绑我的意思是他们可以发送一个包含多个"真实"请求的请求,然后将它们一起发送给客户端.通常是javascript ajax请求.像这样的东西:
POST /bundlerequest
["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10&page=1", "/products", "/product/categories" ]
Run Code Online (Sandbox Code Playgroud)
(捆绑的请求只能是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...
]
}
Run Code Online (Sandbox Code Playgroud)
这种捆绑的好处是它减少了请求的数量,这对移动设备尤其重要.
所以我的第一个问题是,我的方法是一个好的方法吗?有没有人有这样做的经验?
AFAIK解决这个问题的常用方法是编写服务器端代码以返回组合数据,我相信这些数据与客户端相关.(例如Twitter的用户流做到这一点,结合个人信息,最新的鸣叫,最新的个人消息等),但是这使得API非常刚愎自用,当客户需要改变服务器可能需要改变,以适应优化.
第二个问题是如何实现这个?
我的后台是ASP.NET MVC 3和IIS 7.我应该落实在应用程序中,有一个bundlerequest动作在内部调用请求中指定的其他动作?
它可以直接在IIS 7中实现吗?编写一个模块,透明地拦截对/ bundlerequest的请求,然后调用所有相应的子请求,使应用程序完全不知道发生的捆绑?这也允许我以与应用程序无关的方式实现它.
您可以使用异步控制器来聚合服务器上的这些请求。让我们首先定义一个将由控制器返回的视图模型:
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; }
}
Run Code Online (Sandbox Code Playgroud)
然后控制器:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以调用它:
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
}
});
Run Code Online (Sandbox Code Playgroud)
当然,可能需要进行一些调整才能适应您的特定需求。例如,您提到发送会话过期的 AJAX 请求,这可能会重定向到登录页面,因此不会捕获错误。这确实是 ASP.NET 中的 PITA。Phil Haack在博客中发布了一种以 RESTful 方式规避这种不良行为的可能方法。您只需向请求添加自定义标头即可。