I am making an app that will trigger timers on my webpage whenever certain voice commands are spoken with the help of
using System.Speech.Recognition;
My voice recognition works. Goal: Every time a phrase is recognized, to send that phrase to the view, where I can start and stop timers based on the recognized phrase.
I know how to simply send a model to my view:
return View(SpeechRecognition.RecognizedPhrase);
But how can I accomplish sending a new phrase to my view everytime one is recognized? I dont want to return a new View every time, I just want to update the current model already inside my view every time a new phrase is recognized. How can I accomplish this?
My Controller
public class MapsController : Controller
{
// GET: Maps
public async Task<ActionResult> CursedHollow()
{
try
{
SpeechRecognition.GrammarRecognized += OnGrammarRecognized;
var t = Task.Run(() => SpeechRecognition.Init());
t.Wait();
if(!SpeechRecognition.isCurrentlyListening)
{
SpeechRecognition.StartListening();
}
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
//****I WANT TO SEND THE UPDATED RECOGNIZED PHRASE HERE, BUT NOT RETURN A WHOLE VIEW
return View(SpeechRecognition.RecognizedPhrase);
}
public static void OnGrammarRecognized(SpeechRecognizedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Recognized grammar from controller: " + SpeechRecognition.RecognizedPhrase);
}
}
Use PartialView and Update the Partial View using Ajax.
Related
I am working on an ASP.NET MVC web service. In a web page, when a user clicks on a button, this triggers a complex method that takes a bit of time to finish. I want to redirect the user to a waiting page and then, when the process is finished, to redirect the user to a new page.
When the process is done it raises an event, which I can listen to from the controller. But I cannot make the last step to work (the controller redirecting to the new page upon receiving the event).
Here is my very naïve attempt at doing it (with simpler names):
public MyController()
{
EventsControllerClass.ProcessComplete += new EventHandler<MyArgsClass>(OnEventReceived);
}
private void OnEventReceived(object sender, MyArgsClass eventArguments)
{
RedirectToPage();
}
private ActionResult RedirectToPage()
{
return RedirectToAction("PageName");
}
After many days working on this, I have a viable solution. It may not be pretty, but it works, and maybe some ideas can be useful for other people, so here it goes:
I will explain the solution to my particular problem: I need a button to redirect to a "waiting" page while a longer process runs in the background and raises an event when it is finished. When this event is received, we want to redirect the user (automatically) to a final page.
First, I created a class to listen to the event. I tried doing this directly in the controller, but you need to be careful about signing and unsigning, because apparently controllers get created and destroyed at each request. In this "listener class" I have a bool property that is set to "true" when the event is received.
When the first action is triggered, the controller normally redirects to the "wait" page, where I have this simple java script redirecting to the new action:
<script type="text/javascript">
window.location = "#Url.Action("WaitThenRedirect", "AuxiliaryControllerName")";
</script>
This sets in motion the long process (through another event). The key is that I do this with an asynchronous action (this controller inherits from AsyncController). (Note I used an auxiliary controller. This is to keep all asynchronous stuff apart.) This is how this looks (more info here):
public static event EventHandler<AuxiliaryEventsArgs> ProcessReady;
public void WaitThenRedirectAsync()
{
AsyncManager.OutstandingOperations.Increment();
ProcessReady += (sender, e) =>
{
AsyncManager.Parameters["success"] = e.success;
AsyncManager.OutstandingOperations.Decrement();
};
WaitForEvent();
}
public ActionResult WaitThenRedirectCompleted(bool success)
{
if (success)
{
return RedirectToAction("RedirectToView", "ControllerName");
}
else
{
return RedirectToAction("UnexpectedError", "ControllerName");
}
}
private void WaitForEvent()
{
bool isWaitSuccessful = true;
int waitingLoops = 0;
int waitingThreshold = 200;
int sleepPeriod = 100; // (milliseconds)
while (!EventsListener.IsTheThingReady())
{
System.Threading.Thread.Sleep(sleepPeriod);
++waitingLoops;
if (waitingLoops > waitingThreshold)
{
System.Diagnostics.Debug.WriteLine("Waiting timed out!");
isWaitSuccessful = false;
break;
}
}
isWaitSuccessful = true;
if (null != ProcessReady)
{
AuxiliaryEventsArgs arguments = new AuxiliaryEventsArgs();
arguments.success = isWaitSuccessful;
try
{
ProcessReady(null, arguments);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Error in event ProcessReady" + ex);
}
}
}
I believe it is possible to use ajax syntax for alternative solutions, but this is what I have and it works nicely. I believe this is not a very common need, but hopefully someone will benefit!
I'm not sure if this is the correct way to go about the problem I need to solve... however in an OnActionExecuting action filter that I have created, I set a cookie with various values. One of these values is used to determine whether the user is visiting the website for the very first time. If they are a new visitor then I set the ViewBag with some data so that I can display this within my view.
The problem I have is that in some of my controller actions I perform a RedirectToAction. The result is OnActionExecuting is fired twice, once for the original action and then a second time when it fires the new action.
<HttpGet()>
Function Index(ByVal PageID As String) As ActionResult
Dim wo As WebPage = Nothing
Try
wp = WebPages.GetWebPage(PageID)
Catch sqlex As SqlException
Throw
Catch ex As Exception
Return RedirectToAction("Index", New With {.PageID = "Home"})
End If
End Try
Return View("WebPage", wp)
End Function
This is a typical example. I have a data driven website that gets a webpage from the database based on the PageID specified. If the page cannot be found in the database I redirect the user to the home page.
Is it possible to prevent the double firing in anyway or is there a better way to set a cookie? The action filter is used on multiple controllers.
Had the same issue. Resolved by overriding property AllowMultiple:
public override bool AllowMultiple { get { return false; } }
public override void OnActionExecuting(HttpActionContext actionContext)
{
//your logic here
base.OnActionExecuting(actionContext);
}
You can save some flag value into TempData collection of controller on first executing and if this value presented, skip filter logic:
if (filterContext.Controller.TempData["MyActionFilterAttribute_OnActionExecuting"] == null)
{
filterContext.Controller.TempData["MyActionFilterAttribute_OnActionExecuting"] = true;
}
You could return the actual action instead of redirecting to the new action. That way, you dont cause an http-request, thereby not triggering the onactionexecuting (i believe)
Old question, but I just dealt with this so I thought I'd throw in my answer. After some investigating I disovered this was only happening on endpoints that returned a view (i.e. return View()). The only endpoints that had multiple OnActionExecuting fired were HTML views that were composed of partial views (i.e. return PartialView(...)), so a single request was "executing" multiple times.
I was applying my ActionFilterAttribute globally to all endpoints, which was working correctly on all other endpoints except for the view endpoints I just described. The solution was to create an additional attribute applied conditionally to the partial view endpoints.
// Used specifically to ignore the GlobalFilterAttribute filter on an endpoint
public class IgnoreGlobalFilterAttribute : Attribute { }
public class GlobalFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Does not apply to endpoints decorated with Ignore attribute
if (!filterContext.ActionDescriptor.GetCustomAttributes(typeof(IgnoreGlobalFilterAttribute), false).Any())
{
// ... attribute logic here
}
}
}
And then on my partial view endpoints
[HttpGet]
[AllowAnonymous]
[IgnoreGlobalFilter] //HERE this keeps the attribute from firing again
public ActionResult GetPartialView()
{
// partial view logic
return PartialView();
}
I have a system whereby users can upload sometimes large(100-200 MB) files from within an MVC3 application. I would like to not block the UI while the file is uploading, and after some research, it looked like the new AsyncController might let me do what I'm trying to do. Problem is - every example I have seen isn't really doing the same thing, so I seem to be missing one crucial piece. After much futzing and fiddling, here's my current code:
public void CreateAsync(int CompanyId, FormCollection fc)
{
UserProfile up = new UserRepository().GetUserProfile(User.Identity.Name);
int companyId = CompanyId;
// make sure we got a file..
if (Request.Files.Count < 1)
{
RedirectToAction("Create");
}
HttpPostedFileBase hpf = Request.Files[0] as HttpPostedFileBase;
if (hpf.ContentLength > 0)
{
AsyncManager.OutstandingOperations.Increment();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, e) =>
{
string fileName = hpf.FileName;
AsyncManager.Parameters["recipientId"] = up.id;
AsyncManager.Parameters["fileName"] = fileName;
};
worker.RunWorkerCompleted += (o, e) => { AsyncManager.OutstandingOperations.Decrement(); };
worker.RunWorkerAsync();
}
RedirectToAction("Uploading");
}
public void CreateCompleted(int recipientId, string fileName)
{
SystemMessage msg = new SystemMessage();
msg.IsRead = false;
msg.Message = "Your file " + fileName + " has finished uploading.";
msg.MessageTypeId = 1;
msg.RecipientId = recipientId;
msg.SendDate = DateTime.Now;
SystemMessageRepository.AddMessage(msg);
}
public ActionResult Uploading()
{
return View();
}
Now the idea here is to have the user submit the file, call the background process which will do a bunch of things (for testing purposes is just pulling the filename for now), while directing them to the Uploading view which simply says "your file is uploading...carry on and we'll notify you when it's ready". The CreateCompleted method is handling that notification by inserting a message into the users's message queue.
So the problem is, I never get the Uploading view. Instead I get a blank Create view. I can't figure out why. Is it because the CreateCompleted method is getting called which shows the Create view? Why would it do that if it's returning void? I just want it to execute silently in the background, insert a message and stop.
So is this the right approach to take at ALL? my whole reason for doing it is with some network speeds, it can take 30 minutes to upload a file and in its current version, it blocks the entire application until it's complete. I'd rather not use something like a popup window if I can avoid it, since that gets into a bunch of support issues with popup-blocking scripts, etc.
Anyway - I am out of ideas. Suggestions? Help? Alternate methods I might consider?
Thanks in advance.
You are doing it all wrong here. Assume that your action name is Create.
CreateAsync will catch the request and should be a void method and returns nothing. If you have attributes, you should apply them to this method.
CreateCompleted is your method which you should treat as a standard controller action method and you should return your ActionResult inside this method.
Here is a simple example for you:
[HttpPost]
public void CreateAsync(int id) {
AsyncManager.OutstandingOperations.Increment();
var task = Task<double>.Factory.StartNew(() => {
double foo = 0;
for(var i = 0;i < 1000; i++) {
foo += Math.Sqrt(i);
}
return foo;
}).ContinueWith(t => {
if (!t.IsFaulted) {
AsyncManager.Parameters["headers1"] = t.Result;
}
else if (t.IsFaulted && t.Exception != null) {
AsyncManager.Parameters["error"] = t.Exception;
}
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult CreateCompleted(double headers1, Exception error) {
if(error != null)
throw error;
//Do what you need to do here
return RedirectToAction("Index");
}
Also keep in mind that this method will still block the till the operation is completed. This is not a "fire and forget" type async operation.
For more info, have a look:
Using an Asynchronous Controller in ASP.NET MVC
Edit
What you want here is something like the below code. Forget about all the AsyncController stuff and this is your create action post method:
[HttpPost]
public ActionResult About() {
Task.Factory.StartNew(() => {
System.Threading.Thread.Sleep(10000);
if (!System.IO.Directory.Exists(Server.MapPath("~/FooBar")))
System.IO.Directory.CreateDirectory(Server.MapPath("~/FooBar"));
System.IO.File.Create(Server.MapPath("~/FooBar/foo.txt"));
});
return RedirectToAction("Index");
}
Notice that I waited 10 seconds there in order to make it real. After you make the post, you will see the it will return immediately without waiting. Then, open up the root folder of you app and watch. You will notice that a folder and file will be created after 10 seconds.
But (a big one), here, there is no exception handling, a logic how to notify user, etc.
If I were you, I would look at a different approach here or make the user suffer and wait.
I have an action like shown below. In GetAvailableBookList, I get the list and if there is not any available book redirect to a message page. But in action part code continues to execute and gets an exception and I find myself in error page.
I don't want to use return RedirectToAction or something like that because there are a lot of places where we use this redirect logic in our application.
public ActionResult ActionName()
{
List<BookType> bookList = GetAvailableBookList();
// some code
return View("RelatedView");
}
private List<BookType> GetAvailableBookList()
{
....
list = GetList();
if(list.Count == 0)
{
System.Web.HttpContext.Current.Response.Redirect(messagePageUrl, true);
}
else return list;
}
Unfortunately, Response.Redirect() isn't really friendly with ASP.NET MVC. My rule of thumb is if it comes from HttpContext I don't want to touch it in the controller (of course there are many exceptions to that rule) -- especially since it improves testability.
My suggestion is to use RedirectToAction, but since you don't want to repeat code you can do it in such a way that you don't have to repeat code (although in this case I don't see a problem with repeating code).
public ActionResult LoadBookListAndContinue(
Func<List<BookType>, ActionResult> continuation)
{
var list = LoadBooklist();
if(list.Any())
{
return action(continuation);
}
return new RedirectResult(messagePageUrl);
}
// in your controller
public ActionResult ActionName()
{
return LoadBookListAndContinue(
list => {
// some code
return View("RelatedView");
});
}
Is it pretty? No, but it works better than the Redirect exception.
Use
return RedirectToAction("NoListAvailable");
if you have a specific action you would like to execute. The NoListAvailable action can return a view indicating the problem.
Alternatively, you could return the view directly
return View("NoListAvailable");
The exception you are getting is probably ThreadAbortException and this is something you cannot avoid unless you allow the thread to continue (2nd argument in Response.Redirect).
On a side note your current solution is generally flawed. You should use RedirectToAction in each action when your method returns an empty list.
Throwing a specific exception and redirect where you catch it may be solution
Try to write
System.Web.HttpContext.Current.Response.Redirect(messagePageUrl, false);
I want to prevent users submitting forms multiple times in .NET MVC. I've tried several methods using Javascript but have had difficulties getting it to work in all browsers. So, how can I prevent this in my controller? It there some way that multiple submissions can be detected?
Updated answer for ASP.NET Core MVC (.NET Core & .NET 5.0)
Update note: Remember ASP.NET Core is still called "Core" in .NET 5.0.
I'm going to stick to the least-impact use case like before, where you're only adorning those controller actions that you specifically want to prevent duplicate requests on. If you want to have this filter run on every request, or want to use async, there are other options. See this article for more details.
The new form tag helper now automatically includes the AntiForgeryToken so you no longer need to manually add that to your view.
Create a new ActionFilterAttribute like this example. You can do many additional things with this, for example including a time delay check to make sure that even if the user presents two different tokens, they aren't submitting multiple times per minute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class PreventDuplicateRequestAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext context) {
if (context.HttpContext.Request.HasFormContentType && context.HttpContext.Request.Form.ContainsKey("__RequestVerificationToken")) {
var currentToken = context.HttpContext.Request.Form["__RequestVerificationToken"].ToString();
var lastToken = context.HttpContext.Session.GetString("LastProcessedToken");
if (lastToken == currentToken) {
context.ModelState.AddModelError(string.Empty, "Looks like you accidentally submitted the same form twice.");
}
else {
context.HttpContext.Session.SetString("LastProcessedToken", currentToken);
}
}
}
}
By request, I also wrote an asynchronous version which can be found here.
Here's a contrived usage example of the custom PreventDuplicateRequest attribute.
[HttpPost]
[ValidateAntiForgeryToken]
[PreventDuplicateRequest]
public IActionResult Create(InputModel input) {
if (ModelState.IsValid) {
// ... do something with input
return RedirectToAction(nameof(SomeAction));
}
// ... repopulate bad input model data into a fresh viewmodel
return View(viewModel);
}
A note on testing: simply hitting back in a browser does not use the same AntiForgeryToken. On faster computers where you can't physically double click the button twice, you'll need to use a tool like Fiddler to replay your request with the same token multiple times.
A note on setup: Core MVC does not have sessions enabled by default. You'll need to add the Microsoft.AspNet.Session package to your project, and configure your Startup.cs properly. Please read this article for more details.
Short version of Session setup is:
In Startup.ConfigureServices() you need to add:
services.AddDistributedMemoryCache();
services.AddSession();
In Startup.Configure() you need to add (before app.UseMvc() !!):
app.UseSession();
Original answer for ASP.NET MVC (.NET Framework 4.x)
First, make sure you're using the AntiForgeryToken on your form.
Then you can make a custom ActionFilter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class PreventDuplicateRequestAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (HttpContext.Current.Request["__RequestVerificationToken"] == null)
return;
var currentToken = HttpContext.Current.Request["__RequestVerificationToken"].ToString();
if (HttpContext.Current.Session["LastProcessedToken"] == null) {
HttpContext.Current.Session["LastProcessedToken"] = currentToken;
return;
}
lock (HttpContext.Current.Session["LastProcessedToken"]) {
var lastToken = HttpContext.Current.Session["LastProcessedToken"].ToString();
if (lastToken == currentToken) {
filterContext.Controller.ViewData.ModelState.AddModelError("", "Looks like you accidentally tried to double post.");
return;
}
HttpContext.Current.Session["LastProcessedToken"] = currentToken;
}
}
}
And on your controller action you just...
[HttpPost]
[ValidateAntiForgeryToken]
[PreventDuplicateRequest]
public ActionResult CreatePost(InputModel input) {
...
}
You'll notice this doesn't prevent the request altogether. Instead it returns an error in the modelstate, so when your action checks if ModelState.IsValid then it will see that it is not, and will return with your normal error handling.
I've tried several methods using Javascript but have had difficulties getting it to work in all browsers
Have you tried using jquery?
$('#myform').submit(function() {
$(this).find(':submit').attr('disabled', 'disabled');
});
This should take care of the browser differences.
Just to complete the answer of #Darin, if you want to handle the client validation (if the form has required fields), you can check if there's input validation error before disabling the submit button :
$('#myform').submit(function () {
if ($(this).find('.input-validation-error').length == 0) {
$(this).find(':submit').attr('disabled', 'disabled');
}
});
What if we use $(this).valid()?
$('form').submit(function () {
if ($(this).valid()) {
$(this).find(':submit').attr('disabled', 'disabled');
}
});
Strategy
The truth is that you need several lines of attack for this problem:
The Post/Redirect/Get (PRG) pattern is not enough by itself. Still, it should always be used to provide the user with good experiences when using back, refresh, etc.
Using JavaScript to prevent the user from clicking the submit button multiple times is a must because it provides a much less jarring user experience compared to server-side solutions.
Blocking duplicate posts solely on the client side doesn't protect against bad actors and does not help with transient connection problems. (What if your first request made it to the server but the response did not make it back to the client, causing your browser to automatically resend the request?)
I'm not going to cover PRG, but here are my answers for the other two topics. They build upon the other answers here. FYI I'm using .NET Core 3.1.
Client-Side
Assuming you are using jQuery validation, I believe this is the cleanest/most efficient way to prevent your form submit button from being double-clicked. Note that submitHandler is only called after validation has passed, so there is no need to re-validate.
$submitButton = $('#submitButton');
$('#mainForm').data('validator').settings.submitHandler = function (form) {
form.submit();
$submitButton.prop('disabled', true);
};
An alternative to disabling the submit button is to show an overlay in front of the form during submission to 1) block any further interaction with the form and 2) communicate that the page is "doing something." See this article for more detail.
Server-Side
I started off with Jim Yarbro's great answer above, but then I noticed Mark Butler's answer pointing out how Jim's method fails if someone submits forms via multiple browser tabs (because each tab has a different token and posts from different tabs can be interlaced). I confirmed that such a problem really does exist and then decided to upgrade from tracking just the last token to tracking the last x tokens.
To facilitate that, I made a couple of helper classes: one for storing the last x tokens and one for making it easy to store/retrieve objects to/from session storage. The main code now checks that the current token is not found in the token history. Other than that, the code is pretty much the same. I just made some little tweaks to suit my tastes. I included both the regular and asynchronous versions. The full code is below, but these are the critical lines:
var history = session.Get<RotatingHistory<string>>(HistoryKey) ?? new RotatingHistory<string>(HistoryCapacity);
if (history.Contains(token))
{
context.ModelState.AddModelError("", DuplicateSubmissionErrorMessage);
}
else
{
history.Add(token);
}
Sadly, the fatal flaw of this approach is that the feedback from the first post (before any duplicates) gets lost. A better (but much more complex) solution would be to store the result of each unique request by GUID, and then handle duplicate requests by not only skipping doing the work again but also returning the same result from the first request, giving the user a seamless experience. This thorough article detailing Air BnB's methods of avoiding duplicate payments will give you an idea of the concepts.
PreventDuplicateFormSubmissionAttribute.cs
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
// This class provides an attribute for controller actions that flags duplicate form submissions
// by adding a model error if the request's verification token has already been seen on a prior
// form submission.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class PreventDuplicateFormSubmissionAttribute: ActionFilterAttribute
{
const string TokenKey = "__RequestVerificationToken";
const string HistoryKey = "RequestVerificationTokenHistory";
const int HistoryCapacity = 5;
const string DuplicateSubmissionErrorMessage =
"Your request was received more than once (either due to a temporary problem with the network or a " +
"double button press). Any submissions after the first one have been rejected, but the status of the " +
"first one is unclear. It may or may not have succeeded. Please check elsewhere to verify that your " +
"request had the intended effect. You may need to resubmit it.";
public override void OnActionExecuting(ActionExecutingContext context)
{
HttpRequest request = context.HttpContext.Request;
if (request.HasFormContentType && request.Form.ContainsKey(TokenKey))
{
string token = request.Form[TokenKey].ToString();
ISession session = context.HttpContext.Session;
var history = session.Get<RotatingHistory<string>>(HistoryKey) ?? new RotatingHistory<string>(HistoryCapacity);
if (history.Contains(token))
{
context.ModelState.AddModelError("", DuplicateSubmissionErrorMessage);
}
else
{
history.Add(token);
session.Put(HistoryKey, history);
}
}
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
HttpRequest request = context.HttpContext.Request;
if (request.HasFormContentType && request.Form.ContainsKey(TokenKey))
{
string token = request.Form[TokenKey].ToString();
ISession session = context.HttpContext.Session;
await session.LoadAsync();
var history = session.Get<RotatingHistory<string>>(HistoryKey) ?? new RotatingHistory<string>(HistoryCapacity);
if (history.Contains(token))
{
context.ModelState.AddModelError("", DuplicateSubmissionErrorMessage);
}
else
{
history.Add(token);
session.Put(HistoryKey, history);
await session.CommitAsync();
}
await next();
}
}
}
RotatingHistory.cs
using System.Linq;
// This class stores the last x items in an array. Adding a new item overwrites the oldest item
// if there is no more empty space. For the purpose of being JSON-serializable, its data is
// stored via public properties and it has a parameterless constructor.
public class RotatingHistory<T>
{
public T[] Items { get; set; }
public int Index { get; set; }
public RotatingHistory() {}
public RotatingHistory(int capacity)
{
Items = new T[capacity];
}
public void Add(T item)
{
Items[Index] = item;
Index = ++Index % Items.Length;
}
public bool Contains(T item)
{
return Items.Contains(item);
}
}
SessonExtensions.cs
using System.Text.Json;
using Microsoft.AspNetCore.Http;
// This class is for storing (serializable) objects in session storage and retrieving them from it.
public static class SessonExtensions
{
public static void Put<T>(this ISession session, string key, T value) where T : class
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T Get<T>(this ISession session, string key) where T : class
{
string s = session.GetString(key);
return s == null ? null : JsonSerializer.Deserialize<T>(s);
}
}
You could include a hidden (random or counter) value in the form post, a controller could track these values in an 'open' list or something similar; every time your controller hands out a form it embeds a value, which it tracks allowing one post use of it.
In its self, no, however depending on what the controller is actually doing, you should be able to work out a way.
Is a record being created in the database that you can check for to see if they've already submitted the form?
Just add this code at the end of your page. I am using "jquery-3.3.1.min.js" and "bootstrap 4.3.1"
<script type="text/javascript">
$('form').submit(function () {
if ($(this).valid()) {
$(this).find(':submit').attr('disabled', 'disabled');
}
});
</script>
Use the Post/Redirect/Get design pattern.
PS:
It looks to me that the answer by Jim Yarbro could have a fundamental flaw in that the __RequestVerificationToken stored in the HttpContext.Current.Session["LastProcessedToken"] will be replaced when a second form is submitted (from say another browser window). At this point, it is possible to re-submit the first form without it being recognized as a duplicate submission. For the proposed model to work, wouldn’t a history of __RequestVerificationToken be required? This doesn't seem feasible.
Dont reinvent the wheel :)
Use the Post/Redirect/Get design pattern.
Here you can find a question and an answer giving some suggestions on how to implement it in ASP.NET MVC.
You can also pass some sort of token in a hidden field and validate this in the controller.
Or you work with redirects after submitting values. But this get's difficult if you take heavily advantage of ajax.
This works on every browser
document.onkeydown = function () {
switch (event.keyCode) {
case 116: //F5 button
event.returnValue = false;
event.keyCode = 0;
return false;
case 82: //R button
if (event.ctrlKey) {
event.returnValue = false;
event.keyCode = 0;
return false;
}
}
}
You can do this by creating some sort of static entry flag that is user specific, or specific to whatever way you want to protect the resource. I use a ConcurrentDictionary to track entrance. The key is basically the name of the resource I'm protecting combined with the User ID. The trick is figuring out how to block the request when you know it's currently processing.
public async Task<ActionResult> SlowAction()
{
if(!CanEnterResource(nameof(SlowAction)) return new HttpStatusCodeResult(204);
try
{
// Do slow process
return new SlowProcessActionResult();
}
finally
{
ExitedResource(nameof(SlowAction));
}
}
Returning a 204 is a response to the double-click request that will do nothing on the browser side. When the slow process is done, the browser will receive the correct response for the original request and act accordingly.
Use this simple jquery input field and will work awesomely even if you have multiple submit buttons in a single form.
$('input[type=submit]').click(function () {
var clickedBtn = $(this)
setTimeout(function () {
clickedBtn.attr('disabled', 'disabled');
}, 1);
});