I've got an MVC controller that can be called via form submission in a couple different places. The controller then renders a view whose primary purpose is to allow the user to send the document or post it to an external site, as well as fill in text fields that will be used in the notification email.
I am performing validation on these fields - the user can enter custom subject/body text. If they do not, they will receive a popup alert and can either return to the form or submit it using default text indicated in the placeholder value.
The problem is that when the user first reaches this page and clicks the send button, no input in the textboxes is actually registering and it gives the empty string notification regardless of what is actually in the fields; however, if I hit F5 and try again, the input works perfectly.
I feel like this has something to do with the form submissions that initially call this controller being done via POST action, whereas it seems to work fine with the GET on page refresh. I just can't figure out how to either get the content to respond properly when the controller is called via POST, or how to submit the form without posting the data.
Thanks in advance for reading and any help.
Here is the calling controller:
public ActionResult Index(FormCollection collection)
{
//modelbuilding code
return View (Model);
}
The code that calls the controller always uses this format: (in this case, it would be called from the Recipients/Index view.
#using(Html.BeginForm("Index", "Distribution", FormMethod.Post )) {
//form values
<input type="submit" data-role="button" value="Done"/>
}
Here is the relevant part of the view and the JS validation function:
<div id="SubjectTemplate">
<p>Subject: <input id="emailSubjectTextBox" name="EmailSubject" placeholder="#EmailSubject" /></p>
</div>
Send Notification
<script>
function validateInput() {
var possibleErrors = [];
if (!(document.getElementById('emailSubjectTextBox').value)) {
possibleErrors.push('#incompleteEmailSubject' + '\n');
}
//more validation that works the same way and has the same problem
if (possibleErrors.length > 0) {
if (confirm(possibleErrors))
{
window.location.href = '#Url.Action("Send")'
}
}
else {
window.location.href = '#Url.Action("Send")'
}
}
</script>
I'm not sure I fully understand your question, but generally speaking you should not use the same action for POST and GET.
Even more importantly, you should not be using POST if your action does not have some kind of side effect. If all you are doing with your form submission is making some kind of choice then you should be using GET.
See the following post for more information and examples of how to perform ajax GET requests using jQuery: http://www.jquery4u.com/ajax/key-differences-post/
Regardless of what you are trying to do, it is very good practice that POST calls perform some action, and then redirect to a GET action which returns to the user.
[HttpPost]
public ActionResult Index(FormCollection collection)
{
//do whatever you need to save the record
return RedirectToAction("Index")
}
[HttpGet]
public ActionResult Index()
{
//modelbuilding code
return View (Model);
}
Not only does this help your controller adhere to a RESTful methodology, it also stops the user getting that annoying 'Are you sure you want to resubmit this page' if they happen to press f5
Hope this helps.
I found the problem resulted from jQueryMobile automatically utilizing AJAX to post forms. I solved this by including new {data_ajax = "false"} to the Html.BeginForm statements.
Related
So I've created a table to hold extra information for all authenticated users. This table also links up to the many others in my db. This table is hooked up to asp.net identity through the user id although there are multiple fields which share the same information as the membership tables (email and username as well). Unfortunately there was a bug that erased some of this membership data from the users table I added and not the identity tables themselves. The bug itself has been since been fixed, however I am trying to create a way to retrieve this lost information from the membership tables. The way I went about doing so was by adding a button to the edit screen of the users (Not the usersadmin page but the users table I added). My code for the button taking me to the action looks like this:
Button to action
The UserReset Action code looks like this:
UserReset Action Code
The trouble I am having currently is actually being able to call to this action (or even open the edit page at this point). Every time I try to load the page it throws a "Public Action Method not found in controller" error. I feel it's a rookie mistake on my end but can anyone please tell me what I am doing wrong?
I'm going to hold my tongue on the backstory and just answer the question:
So, you have two major things I found. The first is the CSHTML (but not the direct cause of your current specific error). See farther below for the CSHTML suggestions (especially if you run into more problems after the C# Action fixes)
First, your controller. If you look at your UserReset action, you'll notice you decorated it with [HttpPost]. As you said, you can't open the edit page. This is because the edit page action doesn't exist (e.g., the [HttpGet] action at the requested Url). This is what you need:
public class TSTUsersController : IController
{
...
//You need this action to process the get request
[HttpGet]
public ActionResult UserReset()
{
return View("UserReset"); //return the edit form html to the user
}
//this method will handle the button click
[HttpPost]
public ActionResult UserReset( String email )
{
if(ModelState.IsValid)
{
//save the information to the database
//direct the user to some sort of confirmation page
return RedirectToAction("Index", "Home");
}
//return the form with the error messages
return View("UserReset", email);
}
...
}
From what I can tell, you are completely misunderstanding HTML form submission.
The <form></form> element has two main parameters you are missing:
<form
method="POST"
action="#Url.Action("UserReset", 'TSTUsers")" //e.g. POST /TSTUsers/UserReset
... >
...
<button>Submit</button>
</form>
Or, using helpers:
#using( Html.BeginForm( "UserReset", "TSTUsers", FormMethod.Post ) )
{
<button>Submit</button>
}
Now, this would post to the specified action. To add parameters, in your case, your using a non-changing parameter (e.g., the user can't enter an email), so you have two options. You can modify your action parameter to instead designate the parameter (please note, that the user would see this Url upon a non-ajax post, if that matters to you), like so:
<form
action="#Url.Action("UserReset", 'TSTUsers", new { email = Model.Email })"
//e.g. POST /TSTUsers/UserReset?email=example#example.com
... >
...
<button>Submit</button>
</form>
Or, using helpers:
#using( Html.BeginForm( "UserReset", "TSTUsers", FormMethod.Post, new { email = Model.Email } ) )
{
<button>Submit</button>
}
Now, if you would prefer to hide the Url parameter from the request (for whatever reason), then you would instead add a input, with the type of hidden:
<form
action="#Url.Action("UserReset", 'TSTUsers")" //e.g. "POST /TSTUsers/UserReset
... >
<input type="hidden" name="email" value="#Model.Email"
<button>Submit</button>
</form>
Or, using helpers:
#using( Html.BeginForm( "UserReset", "TSTUsers", FormMethod.Post ) )
{
#Html.Hidden("email", Model.Email )
<button>Submit</button>
}
I know that maybe the title sounds a bit weird but I believe that my problem is weird indeed. I have an ASP.NET MVC 4 application (this is my first MVC real-world application) with Razor view-engine.
I have a layout view where I'm rendering two partial views like this:
<!-- Login -->
#Html.Action("RenderLoginPopup", "Login")
<!-- Registration -->
#Html.Action("RenderRegisterPopup", "Login")
Each of those actions from the Login controller just renders a partial view:
[ChildActionOnly]
public ActionResult RenderLoginPopup()
{
return PartialView("Partial/_LoginPopupPartial");
}
Just for exemplification sake (both are built the same way), the login partial view contains an ajax form like this:
#using (Ajax.BeginForm("Login", "Login", new AjaxOptions()
{
HttpMethod = "POST",
OnSuccess = "loginResponseReceived"
}, new { #id = "loginForm" }))
The Login action from the Login controller (the target of the form) is signed with the following attributes (worth to mention and notice the HttpPost one):
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public JsonResult Login(LoginModel model)
{ }
So far, so good... Everything works perfect - both the login and the register actions are working without any issues.
The issue that I want to speak about shows-up when I have a #Html.BeginForm() in a view that is loaded along with the main layout. For example, if I have a pure and simple form like this:
#using (Html.BeginForm())
{
<input type="hidden" name="name"/>
<input type="submit" value="Send"/>
}
along with the controller CaptionExtendedController:
[HttpPost]
public ActionResult Index(string nume)
{
return View();
}
So, in the end, in my final html generated file I will have 3 forms - 2 for login and register (ajax) and one simple form generated from the last view. Please keep in mind that all three forms are independent (meaning that they are not one in another).
The issue is that everytime I'm pressing the button "Send" from the last form all controllers that are signed with the [HttpPost] attribute from my view (Login, Register from LoginController and Index from CaptionExtendedController) gets called.
WHY??? In order to have a temporary fix, I've removed the [HttpPost] attribute from the Login and Register actions and now it's working but I don't think this is correct.
Please, there is someone who can explain me why this is happening and eventually point me to the right direction in fixing this issue?
Thank you in advance.
Try specifying the controller and action with your Html.BeginForm and we can start from there. Also, you can utilize #Html.RenderPartial to render your partials which would get rid of some of your unneeded actions/controllers, making it a bit more manageable.
This doesn't address the root problem, but might be a work-around which is all you need anyway. :)
You could write some jQuery which catches the button click and then submits the form directly by name. For example, if you add the id below:
#using (Html.BeginForm())
{
<input type="hidden" name="name"/>
<input type="submit" id="regularSubmitButton" value="Send"/>
}
Then you could write:
$(document).ready(function() {
$("#regularSubmitButton").click(function(e) {
e.preventDefault();
$(this).parent("form").submit();
return false;
});
});
I'm not sure it would work without seeing everything, but seems to be worth a try.
Cheers,
Michael
It doesn't make sense anymore... Starting from the comments and answers, I've mapped 3 functions to the submits on those tricky forms: login, register and index (also I've put back the HttpPost attribute to the Login and Register actions). In those jquery functions I've just put an alert with a string (name of the form); in order to be able to write a jquery id-based selector, I've declared also the last form with an id (it didn't had it; only login and register had one), like this (an example taken from another form with the same issue):
#using (Html.BeginForm("Index", "PersonalizeCard", new {data = Model.EncryptedDataQueryStringValue}, FormMethod.Post, new {#id = "personalizeCardForm"})) { }
(what has been added - last two parameters - form method and html id).
After this, I've run the application and no exception anymore... I've put breakpoints on the login and register actions - nothing... I've even removed those 2 extra parameters from the BeginForm - still nothing...
WHY???? Again, why??? I mean, I'm not upset that it's fixed but I don't understand why it's fixed by itself...
THANK YOU FOR ALL YOUR TIME AND COMMENTS / ANSWERS.
I have a view with the following code:
#using (Html.BeginForm("GenerateQrCode", "QrCodeGenerator", FormMethod.Post ))
{
<input type="submit" value="Generate" />
}
It's just a submit button, which calls the code in my controller:
public void GenerateQrCode()
{
}
Is it possible for a method in my controller to return a value from the form, but without going to a different page? Because I notice that currently, on pressing the form button it tries to navigate to a non-existing 'GenerateQrCode' page (the same name as the controller method).
UPDATE:
Something I've tried is making the controller method return an ActionResult, and return a 'RedirectToAction', and simply calling the same view. However, I also had code in this method 'ViewBag.Message = "myMessage"; and then in my view I had the code '#ViewBag.Message', so I hoped that the view would update with the ViewBag message property, but it doesn't appear so.
The version of Html.BeginForm you're using tells the submit button to post to the GenerateQrCode action. Perhaps you could try another overload of Html.BeginForm that better suits your purposes?
Using javascript:
Handle the click event.
In the handler, prevent the default action and stop the event propagation.
Make and AJAX call to the server with the form data.
Get the response and take further actions.
So if I have this right, you want to submit an action without actually taking into account the response from the server. Sounds like you need jQuery post:
$.post(url, $("form selector").serialize(), function () {
// This is where you put a callback function.
});
That is all you need. This will post the data. What you can do is call this from a button click event instead of a form submit button. Or, you can override the submit event on the form and put this as your first line of code:
e.preventDefault();
where e is your event arguments. This will prevent the default action from occurring, which in this case is the submission of the form and the loading of the response.
The simplest method of doing this was simply to use the RedirectToAction method, specifying the name of the controller in the parameters. I also used the TempData variable to pass the results (in this case just a string value) back to the view, and into a ViewBag variable.
public ActionResult QrCodeGenerator()
{
ViewBag.Message = TempData["Message"];
return View();
}
public ActionResult GenerateQrCode()
{
TempData["Message"] = "myMessage";
return RedirectToAction("QrCodeGenerator");
}
I'm creating a messaging web app in ASP.NET and are having some problems when displaying an error message to the user if they go to send a message and there is something wrong.
A user can look through profiles of people and then click, 'send a message'. The following action is called (url is /message/create?to=username) and shows them a page where they can enter their message and send it:
public ActionResult Create(string to)
{
ViewData["recipientUsername"] = to;
return View();
}
On the page that is displayed, the username is entered in to a hidden input field. When the user clicks 'send':
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection, string message)
{
try
{
//do message stuff that errors out
}
catch
{
ModelState.AddModelErrors(message.GetRuleViolations()); //adding errors to modelstate
}
return View();
}
So now the error message is displayed to the user fine, however the url is changed in that it no longer has the querystring (/message/create). Again, this would be fine except that when the user clicks the refresh button, the page errors out as the Create action no longer has the 'to' parameter.
So I'm guessing that I need to maintain my querystring somehow. Is there any way to do this or do I need to use a different method altogether?
I assume you are doing something like...
<% Html.BeginForm("Create", "Controller") { %>
<% } %>
As you are creating the Form's action url through routing, the existing route values will be lost in the process. The easiest way to avoid this is by just using the parameterless version of BeginForm, since you are on the page you are posting to.
<% Html.BeginForm() { %>
<% } %>
This will use the current url, query string and all, as the ACTION of the form. Otherwise, you will need to pass in the route value to in the BeginForm overload.
Recommend you take a look at the PRG pattern which will help with this.
http://devlicio.us/blogs/tim_barcz/archive/2008/08/22/prg-pattern-in-the-asp-net-mvc-framework.aspx
I'm new to web programming in general so this is probably a really simple question. I couldn't find anything on the web however.
What I want to do is call my controller with the typed search string and return the results from a database and paginate the results. Right now I am using a function that is called when the button is pressed. The function looks like so:
function SubmitSearch() {
var searchBox = document.getElementById('searchBox');
var searchButton = document.getElementById('SearchButton');
$.post("MyController/MyAction/",
{
searchString: searchBox.value,
page: null
}, function(result) {
$('#searchResults').html(result);
searchButton.value = "Search";
});
}
What happens is my controller is called, and my searchResults div is populated with the results and paginated. The user can click any search result returned to view the details.
The problem is when the user clicks the browsers 'back' button, the page returns to the state before the search was entered, and this is because of the ajax call. What I want to do is, call the controller and have the page load like google would. Instead of using a PartialView, I would use a View (my guess).
How would I call the controller and have the page RELOAD with the results. I must be missing something fundamental because this definitely seems like it should be easy.
If you don't want to use AJAX then you need to place your text field in a form element on your page, something like:
<form action="MyController/MyAction/" method="get">
<input id="SearchBox" name="SearchBox" type="text" />
<button type="submit">Search</button>
</form>
Then in your controller return the view with the list of results.
You probably also want to look into RESTful URLs and the PRG (Post, Redirect, Get) pattern to maintain the integrity of the back button and enable correct bookmarking of pages etc.
I think you might actually be looking for an AJAX History library to help when the Back button is pressed rather than altering your app. Take a look at this blog post.
Aspx:
<% using (Html.BeginForm<MyController>(m => m.MyAction(null)) { %>
<%= Html.TextBox("q"); %>
<% } %>
// Listing
Controller:
public class MyController : Controller
{
public ActionResult MyAction(string q)
{
var repository; // instance of your repository.
if (String.IsNullOrEmpty(q))
{
return View(repository.GetAllBlogs());
}
return View(repository.SearchBlogs(q));
}
}