3

I have the following controller:

[HttpPost]
public ActionResult SomeMethod(string foo, obj bar)
{
  //Some Logic 
}

Now suppose that from the view that Action is called from a Button or from Ajax (with some edits), and I don't want to receive a double request.

What is the best approach to handle it from server side?


Update

You'd first have to define the time interval that would meet the criteria of a double request – Jonesopolis

Let's suppose that in this case a double request are when the difference of time between first and 2nd call is less than 1s

freedomn-m
  • 27,664
  • 8
  • 35
  • 57
Sid
  • 14,176
  • 7
  • 40
  • 48
  • 1
    You'd first have to define the time interval that would meet the criteria of a double request – Jonesopolis Nov 22 '16 at 14:52
  • @Jonesopolis thanks for pointing it out, I will update the question – Sid Nov 22 '16 at 14:53
  • 1
    You can store in session\cache\whatever the time this request was called by given user, ip address, user agent combination. Then you check that cache and if less than 1 second has passed - reject request (or do what you need in this case). More interesting is what leads you to handle this on server side... – Evk Nov 22 '16 at 14:57
  • 1
    Although you put "server side" - generally, this is much easier to handle on the UI, eg debounce the request or disable the button. Not foolproof, but depends on the scenario and normally fine for intranet applications. – freedomn-m Nov 22 '16 at 14:59
  • I am just curious, I know I could block the UI with some JS. But as you know this is not foolproof so I was wondering which could be a good method to do it also server side @Evk – Sid Nov 22 '16 at 15:01
  • Generate unique key from client side and send it to server side with requests. You can check the key and neglect duplicate requests. With this approach users can send multiple requests if they actually needed. If you limit it from server side by time interval then user have to wait even when they different requests. – Damith Nov 22 '16 at 15:01
  • Isn't the obvious answer only submit either the ajax request or the button. I don't understand why or how you'd send both. Fix the client side not the server side – Liam Nov 22 '16 at 15:21
  • @Liam I never said I will send both request – Sid Nov 22 '16 at 15:22
  • So how would you recieve a "double request"? – Liam Nov 22 '16 at 15:22
  • @Liam User double clicking for example – Sid Nov 22 '16 at 15:23
  • Prevent the double click then? Just disable the button after the first click. You're thinking about this all wrong – Liam Nov 22 '16 at 15:24
  • @Liam Please read all the comments, I already said that I know i can simply prevent the double click. It's just a matter of curiosity to know if there's a good way to check it also from server side. – Sid Nov 22 '16 at 15:25
  • So your simply discounting the correct way for some arbitrary reason.... – Liam Nov 22 '16 at 15:29
  • How exactly disabling button after first click is not foolproof? I thought you mayne cannot edit client side or something. But if you can - dont bother with server side (if you really want - generate request id as described above). – Evk Nov 22 '16 at 15:30
  • @Liam I am simply curious to know if some one who has more knowledge than me knows a good or suggested way to do it. Yes you can block the UI, okay but suppose it's something important. Blocking UI is not 100% safe – Sid Nov 22 '16 at 15:31
  • 1
    Possible duplicate of [How do I prevent multiple form submission in .NET MVC without using Javascript?](http://stackoverflow.com/questions/4250604/how-do-i-prevent-multiple-form-submission-in-net-mvc-without-using-javascript) – Luke Nov 22 '16 at 15:31
  • @Evk I think if someone wants to send a double request he can just use for example an Utility like PostMan – Sid Nov 22 '16 at 15:32
  • 1
    Quote from this answer: "[Dont reinvent the wheel :) Use the Post/Redirect/Get design pattern.](http://stackoverflow.com/a/4250838/894792)" – Luke Nov 22 '16 at 15:32
  • 2
    If you want to prevent malicious users to spam your service - that is entirely different story. – Evk Nov 22 '16 at 15:35

3 Answers3

7

Frankly, you can't, at least not totally. There's certain things you can do server-side, but none are fool-proof. The general idea is that you need to identity the POST is some way. The most common approach is to set a hidden input with a GUID or similar. Then, when a request comes in you record that GUID somewhere. This could be in the session, in a database, etc. Then, before processing the request, you check whatever datastore you're using for that GUID. If it exists, it's a duplicate POST, and if not, you can go ahead.

However, the big stipulation here is that you have to record that somewhere, and do that takes some period of time. It might only be milliseconds, but that could be enough time for a duplicate request to come in, especially if the user is double-clicking a submit button, which is most often the cause of a double-submit.

A web server just reponds to requests as they come in, and importantly, it has multiple threads and perhaps even multiple processes serving requests simultaneously. HTTP is a stateless protocol, so the server doesn't care whether the client has made the same request before, because it effectively doesn't know the client has made the same request before. If two duplicate requests are being served virtually simultaneously on two different threads, then it's a race to see if one can set something identifying the other as a duplicate before the other one checks to see if it's a duplicate. In other words, most of the time, you're just going to be out of luck and both requests will go through no matter what you try to do server-side to stop duplicates.

The only reliable way to prevent double submits is to disable the submit button on submit using JavaScript. Then, the user can effectively only click once, even if they double-click. That still doesn't help you if the user disables JavaScript, of course, but that's becoming more and more rare.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • As other answer suggested we can send unique key from client side and check it on server side, if we have unique key or primary key based on that again it will not insert from db side as well. – Damith Nov 22 '16 at 16:13
  • Well that assumes the server is actually writing something to a database, which is not always the case, but yes, if you are writing to a database, you can have a unique column where you store the GUID or whatever that identifies the POST request and a second attempt to write the same GUID will then fail. Of course, you would need to catch the database error at that point and recover gracefully. However, that's really just optimistic concurrency and there's better ways to handle that, such as using a rowversion column. – Chris Pratt Nov 22 '16 at 16:19
4

Look. Becareful with this approach. You will add most complexity to control this in server side.

First, you need recognize when the multiple requests are comming from the same user.

I don't think that to control this in server side is the best way.

However, if you really want that... look: https://stackoverflow.com/a/218919/2892830

In this link was suggested maintain a list of token. But, in your case, just check if the same token was received more than one time.

Community
  • 1
  • 1
Gean Ribeiro
  • 1,025
  • 2
  • 10
  • 23
0

You need at least to implement double click on event listener.

  1. UseSubmitBehiviar="false"
  2. OnClientClick="this.disable='true'; this.value="Please wait";"
  3. Check ASP.NET Life cycle
  4. Check Request/Redirect

enter image description here

  1. Add test code to see who is responsible

    if (IsPostBack) {

    _CtrlName = thisPage.Request.Params.Get("__EVENTTARGET");
    
    if (_CtrlName != null && _CtrlName == myButton.ID)
    {
    
        //Do your thing
    }
    

    }

  2. Check IsPostBack in page load and use it correct to prevent dublicate requests.

    protected void Page_Load(object sender, EventArgs e) {

            if (!IsPostBack)
            {
    
    
            }
        } 
    
Pit J
  • 169
  • 8