我有一个包含数百个复选框和下拉菜单的表单(其中许多值的耦合在一起)。在操作中有更新机制来更新Session中的对象。此对象执行所有值的验证和耦合,例如,如果用户在一个输入字段中键入%50,我们可能会在下拉列表中添加3个新的SelectListItem。
一切正常,但如果使用开始非常快速地点击复选框(这是我们场景中的正常情况),控制器会在处理之前的帖子时获得多个帖子。幸运的是,我们只对最后一个POST感兴趣,因此当来自同一表单的更新请求到来时,我们需要一种方法来中止取消请求。
我尝试了什么: 1-阻止客户端在服务器仍在上一个服务器上工作时发出多个帖子。这是不可取的,因为它会在浏览器端出现明显的暂停。
2-有几种解决方案可以使用HASH代码或AntiForgeryToken阻止多个帖子后备。但是他们并不是我需要的东西,我需要中止正在进行的线程,而不是阻止传入的请求。
3-我尝试通过添加两个消息处理程序(一个在操作之前,另一个在执行操作之后)来扩展管道以保持哈希代码(或AntiForgeryToken)但问题仍然存在,即使我可以检测到正在进行的线程在同一个请求上工作,我无法中止该线程或将旧请求设置为Complete。
有什么想法吗?
答案 0 :(得分:0)
基本上,您需要在单击复选框时设置超时。您可以让初始请求通过,但随后任何其他请求都会排队(或者在您的方案中第一个排队请求之后实际丢弃)并且不会运行该请求直到超时清除。
没有办法中止请求服务器端。每个请求都是幂等的。对于之前或之后发生的任何事情都没有固有的知识。服务器有多个线程来处理请求,并且会尽可能快地处理这些请求。无法处理请求的处理方式或响应的发送方式。第一个请求可能是接收响应的第三个请求,这仅仅取决于每个请求的处理方式。
答案 1 :(得分:0)
您正在尝试通过异步技术实现事务功能(即仅计算最后一个请求)。这是一个设计缺陷。
由于您拒绝在客户端阻止,因此您无法控制哪些请求首先处理,或者在客户端再次正确处理结果。
您可能会遇到这种情况:
阻止是确保订单正确的唯一方法。
答案 2 :(得分:0)
Thanks for your help @xavier-j. After playing around this, I wrote this. Hope it be useful for someone who needs same thing:
First you need add this ActionFilter
public class KeepLastRequestAttribute : ActionFilterAttribute
{
public string HashCode { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
Dictionary<string, CancellationTokenSource> clt;
if (filterContext.HttpContext.Application["CancellationTokensDictionary"] != null)
{
clt = (Dictionary<string, CancellationTokenSource>)filterContext.HttpContext.Application["CancellationTokensDictionary"];
}
else
{
clt = new Dictionary<string, CancellationTokenSource>();
}
if (filterContext.HttpContext.Request.Form["__RequestVerificationToken"] != null)
{
HashCode = filterContext.HttpContext.Request.Form["__RequestVerificationToken"];
}
CancellationTokenSource oldCt = null;
clt.TryGetValue(HashCode, out oldCt);
CancellationTokenSource ct = new CancellationTokenSource();
if (oldCt != null)
{
oldCt.Cancel();
clt[HashCode] = ct;
}
else
{
clt.Add(HashCode, ct);
}
filterContext.HttpContext.Application["CancellationTokensDictionary"] = clt;
filterContext.Controller.ViewBag.CancellationToken = ct;
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
if (filterContext.Controller.ViewBag.ThreadHasBeenCanceld == null && filterContext.HttpContext.Application["CancellationTokensDictionary"] != null) {
lock (filterContext.HttpContext.Application["CancellationTokensDictionary"])
{
Dictionary<string, CancellationTokenSource> clt = (Dictionary<string, CancellationTokenSource>)filterContext.HttpContext.Application["CancellationTokensDictionary"];
clt.Remove(HashCode);
filterContext.HttpContext.Application["CancellationTokensDictionary"] = clt;
}
}
}
}
I am using AntiForgeryToken here as key token, you can add your own custom hash code to have more control.
In the controller you will have something like this
[HttpPost]
[KeepLastRequest]
public async Task<ActionResult> DoSlowJob(CancellationToken ct)
{
CancellationTokenSource ctv = ViewBag.CancellationToken;
CancellationTokenSource nct = CancellationTokenSource.CreateLinkedTokenSource(ct, ctv.Token, Response.ClientDisconnectedToken);
var mt = Task.Run(() =>
{
SlowJob(nct.Token);
}, nct.Token);
await mt;
return null;
}
private void SlowJob(CancellationToken ct)
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(200);
if (ct.IsCancellationRequested)
{
this.ViewBag.ThreadHasBeenCanceld = true;
System.Diagnostics.Debug.WriteLine("cancelled!!!");
break;
}
System.Diagnostics.Debug.WriteLine("doing job " + (i + 1));
}
System.Diagnostics.Debug.WriteLine("job done");
return;
}
And finally in your JavaScript you need to abort ongoing requests, otherwise browser blocks new requests.
var onSomethingChanged = function () {
if (currentRequest != null) {
currentRequest.abort();
}
var fullData = $('#my-heavy-form :input').serializeArray();
currentRequest = $.post('/MyController/DoSlowJob', fullData).done(function (data) {
// Do whatever you want with returned data
}).fail(function (f) {
console.log(f);
});
currentRequest.always(function () {
currentRequest = null;
})
}