3

I have a razor view that renders a html form and it posts to the server. If the form values are right then it gets saved to database. After insertion, I redirect to another view where user can make further changes.

Right now the user can hit browser back button and resubmit the form to create another record in db.

How do I prevent duplicate submission in my MVC app?

4 Answers 4

5

One solution is to put a hidden "token" field on the form that's generated randomly when the form loads. When you see that token come back on creation store it somewhere temporarily (in session if you're using sessions for example). If you see the same one again, you can assume the same form was submitted twice quickly together.

Sign up to request clarification or add additional context in comments.

5 Comments

This would either require you to store the token with the record and check for duplicates, or store the token in session, where duplicate submissions can only be caught in the same session. But otherwise a good solution.
The view is driven by a model (class). How do this be done? I can generate some random string in the controller and assign to some field of the model and also store in session. During saving I can pull from model and compare with the value in session. Is this how to do it? If not, do you have sample code (very high level)?
How do you I generate that token? I am open to use session or cookie.
The token could be anything, like a random number or DateTime.Now.Ticks. I'd either put it in the model or in the ViewBag, then put it on my form as a hidden field. When it's submitted, store that value in session or in the database and compare future form submits against it. If it's in the database you'd also want to purge it after some time, there's no need to keep ones from 6 months ago.
This approach 'can' work but this involves outside tracking if some sort. Then you need to consider app resets..worker process shutdowns, web farms , etc. Using a cookie avoids this. This approach also could fail if this is ok across sessions if the browser caches the old token.
2

Create a cookie to represent that particular page when it succeeds. If it is replayed with the cookie (which the browser would now send over with every request) you know not to allow the new attempt.

2 Comments

what do you store in that cookie? I need a solution that detect and prevent duplicate form submission
Simply the page name or action name or anything at all to describe that action. Just look for that cookie then on post (Request.Cookies["whatever"]) and set it after a successful save using Response.Cookies.Add
2

Redirect the user to another HttpGet action after handling the post request. So that when the user refreshes the browser the post action will not be called again.

return RedirectToAction("YourActionMethod");

1 Comment

How would you handle a browser Back button click?
2

Although client side validation is possible, it is not secure enough. I am not sure if this method applies to MVC 3, but what i did is implement a ActionFilterAttribute

here is the implementation:

 public class PreventFrequentCallsAttribute : ActionFilterAttribute
 {
    public int DelayRequest = 5;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
            var request = filterContext.HttpContext.Request;      
            var cache = filterContext.HttpContext.Cache;

            var originationInfo = request.ServerVariables["HTTP_X_FORWARDED_FOR"] ?? request.UserHostAddress;
            originationInfo += request.UserAgent;
            var targetInfo = request.RawUrl + request.QueryString;
            var hashValue = string.Join("", MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(originationInfo + targetInfo)).Select(s => s.ToString("x2")));

            if (cache[hashValue] != null)
            {    
                filterContext.Controller.ViewData.ModelState.AddModelError("ExcessiveRequests", "Excessive Request Attempts Detected.");                    
            }
            else
            {
                cache.Add(hashValue, originationInfo, null, DateTime.Now.AddSeconds(DelayRequest), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
            }

            base.OnActionExecuting(filterContext);
    }
}

later, in the target controller, just add this attribute:

    [PreventFrequentCalls(3)]
    public PartialViewResult LogOn(LogOnViewModel model)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.