I have two forms on the page. One of them, say MainForm, is using #using (Html.BeginForm... to call an action upon submit, and the other, RadioForm, just consists of a bunch of radios with no submit button (it's used to trigger the display of MainForm). If an error occurs during server-side validation, I return the View from the Controller. This though causes the radio to be deselected.
What I'm thinking is to grab the selected value of RadioForm so that I can set it again after the return. How would I do this? Is there a better way of persisting the state of the RadioForm?
The browser won't post any form values outside of the form that's submitted, so there's no way to do this server-side. One solution would be to maintain a hidden field and update this with jQuery within the main form:
#Html.HiddenFor(m => m.RadioValue, new { #class = "radio-tracker" })
Give each of your radio buttons a class of, say, "radio-hidden" and use the following:
$('.radio-hidden').click(function() {
$('.radio-tracker').val($(this).val());
});
You can then create a property on your main form's model called RadioValue and it will be bound to the selected radio button's value on post.
Then, when you return your view again, you can use this value to pre-populate the radio buttons.
Alternatively, you could look at using AJAX.
Instead of having the radios on a different form you need to have them on the one that submits. This way you can pass those values back to the view.
You can use the Ajax.BeginForm method instead of Html.BeginForm which allows you to specify the update target. This will allow you to update the submitted form only and not affect the radio form.
#using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "result" }))
http://msdn.microsoft.com/en-us/library/system.web.mvc.ajax.ajaxextensions.beginform(v=vs.108).aspx
Related
Update:
What are the control/fields whose value would be submitted when the form is post back?
In an ASP.NET MVC Form, if user double clicks on the submit button, the form would be submitted two times. In order to solve this problem I implemented the solution explained here.
This is my solution, where I disable the submit button on form submit so it cannot be clicked again:
function preventFromBeingDoubleSubmitted() {
$('form').each(function () {
$(this).submit(function (e) {
if ($("form").valid()) {
// if form is valid, then disable the submit button so it cannot be double clicked (double submitted)
$(this).find(':submit').attr('disabled', 'disabled');
}
});
});
}
$(document).ready(function () {
preventFromBeingDoubleSubmitted();
});
This works fine, but I am getting a very strange behavior with ASP.NET Built in, Identity code. My login page, allows user to login with Facebook or Google (each of those buttons are submit buttons):
This is the code which generates the above login form (this is the built-in identity template):
#{
var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes();
if (loginProviders.Count() > 0)
{
using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#foreach (AuthenticationDescription p in loginProviders.OrderBy(o => o.Caption))
{
if (string.Equals(p.AuthenticationType, "google", StringComparison.InvariantCultureIgnoreCase))
{
<button type="submit" class="external-login-btn btn-google" id="#p.AuthenticationType" name="provider" value="#p.AuthenticationType" title="Log in using your #p.Caption account">Log in with #p.AuthenticationType</button>
}
if (string.Equals(p.AuthenticationType, "facebook", StringComparison.InvariantCultureIgnoreCase))
{
<button type="submit" class="external-login-btn btn-facebook" id="#p.AuthenticationType" name="provider" value="#p.AuthenticationType" title="Log in using your #p.Caption account">Log in with #p.AuthenticationType</button>
}
}
</div>
}
}
}
The above code, should hit the following Controller Action (built-in identity template):
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
After, adding the .js code to prevent double submission, the external login no longer works. The problem is, when user clicks on Log in with Facebook button, the provider name is no longer passed in to ExternalLogin Action.
If I remove preventFromBeingDoubleSubmitted() function, provider name would be passed in ExternalLogin Action method and everything works fine.
What I don't understand is, how is provider passed in to action method at the first place? And why disabling the button prevents provider from being passed in?
I will first answer this question:
What I don't understand is, how is provider passed in to action method at the first place?
You have a button with name="provider" value="#p.AuthenticationType" this code is passing the provider name to your action method.
Next:
And why disabling the button prevents provider from being passed in?
When the form is submitted, the value of disabled fields is not passed to the server. This is the default behaviour.
Now to solve it, we can hide the button instead of disabling it. So in your preventFromBeingDoubleSubmitted() you can change $(this).find(':submit').attr('disabled', 'disabled'); to $(this).find(':submit').hide();
Hope this helps.
Update
To answer a new question about which fields are included in the form data.
<input>
<button>
<option>
An HTML form is a section of a document containing normal content, markup, special elements called controls (checkboxes, radio buttons, menus, etc.), and labels on those controls. Users generally "complete" a form by modifying its controls (entering text, selecting menu items, etc.), before submitting the form to an agent for processing (e.g., to a Web server, to a mail server, etc.)
Users interact with forms through named controls.
A control's "control name" is given by its name attribute. The scope of the name attribute for a control within a FORM element is the FORM element.
HTML defines the following control types:
buttons
checkboxes
radio buttons
menus: Menus offer users options from which to choose. The SELECT element creates a menu, in combination with the OPTGROUP and OPTION elements.
input controls (number, text, etc.)
hidden controls
object controls: Authors may insert generic objects in forms such that associated values are submitted along with other controls. Authors create object controls with the OBJECT element.
Because buttons are controls so the button's value will be posted to the server (if the buttons have name and value like your example). So it is about html specification. NOT asp.net mvc, not Microsoft's specification
You can refer these links for more details
https://www.w3.org/TR/html4/interact/forms.html#h-17.2.1
https://www.w3.org/TR/html5/sec-forms.html#submittable-element
I have a textbox set-up, like so:
#Html.TextBoxFor(m => m.NewVehCode, new {data_id = "" })
When a user selects a vehicle, the description of the vehicle becomes the value of the textbox and the vehicle code (which is what I want to send to DB) becomes the data-id (not full code but I set the values like this):
$("#NewVehCode").val(vehDescription);
$("#NewVehCode").attr("data-id", vehCode);
This all works fine, except for the fact that when I submit, MVC grabs the value of the textbox.
Is there a way I can on submit, get the data-id of that textbox instead of the value?
Note that I'm not using .js to gather the data. Form calls a controller action that sends the model directly to the controller.
There is no way to submit values of "data" properties. I am not sure why you wanted to use data property (is that a requirement or not, not sure). But you can have a hidden property in the form so, when user selects a vehicle, along with data property of text box , update this hidden value. This hidden value will be submitted back to form.
Stephen Muecke's comment helped me trigger this solution:
I've added a hidden textbox to use for the vehicle code, and made the textbox that shows the description a standard textbox with no data binding.
<input id="newVehInput" readonly="readonly" class="longInput" type="text">
#Html.TextBoxFor(m => m.NewVehCode, new { style = "display: none"})
This way I can use the value with no issues:
$("#newVehInput").val(vehDescription);
$("#NewVehCode").val(vehCode);
(Will mark as answer in 2 days)
Add a hidden textbox to your html which you post back on submit
In your model:
public string Id {get;set;}
Render it in the view with a hidden class
And with js.
$("#vehicleList").on("change",function(){
$("#Id").val($vehCode)
})
When you submit the value of id should be set in your model
You can have a hidden field hold the Updated value.
HTML:
<input type="hidden" id="HiddenVehCode" name="HiddenVehCode" Value="0"/>
JS
$("#HiddenVehCode").val(vehDescription);
Note: you should have the model set up so that HiddenVehCode will reach the actionMethod on Form Submit.Something Like,
public int HiddenVehCode {get;set;}
I am writing a recipe manager for my wife in C#/.NET MVC 5. I'm getting to the page for creating a recipe, and I'm a little stumped. A recipe consists of a Name and a list of ingredients.
When I create a view, I have my form:
#using(Html.BeginForm()){
//Form elements
#Html.DisplayNameFor(x => Model.Name)
//button for adding a new ingredient to the recipe
<input type="submit" text="Submit New Recipe!" />
}
When the button for adding an ingredient is clicked, it should render a block of html inside the form just above the button itself, that way the user can add any number of ingredients and then submit the recipe back when the form is posted back to the controller.
For this functionality, should I make the button call a controller that sends back a partial view or something? I'm not sure how to accomplish this outside of JavaScript, but I'm wanting to use a .NET MVC solution if I can.
I try to minimize my reliance on javascript whenever I can, however I agree with #br4d that knockout is your best option here. If you want to avoid it at all cost, it will be more complex, slower and not as user friendly but here is how I would do it.
Enclose the form in a div. Have a place holder div inside the form to hold your ingredients list. Make the "Add new ingredient" call into a controller that will return a partial view with the required fields. In the target attribute indicate the place holder div as the update target and append the response to the html of the place holder div by specifying InsertionMode.InsertAfter.
<div id="parentDiv">
#Html.BeginForm........
#Ajax.ActionLink("Add New Ingredient","ActionName","ControllerName",routeValues,
new AjaxOptions
{
UpdateTargetId = "ChildDiv",
InsertionMode = InsertionMode.InsertAfter
}
<div id=ChildDiv>
</div>
</div>
This code is by no means comprehensive or production ready (I prefer to have a way of handling failed ajax calls and you might want to block off interactions until the call comes back just to mention two of the enhancements). Once again KnockOut would be the preferred way to do this.
Let's assume I have 2 Controllers, TopicsController and PostsController.
For each controller, I have a couple of views (Index & Details).
The Topic (Index) view inherits System.Web.Mvc.ViewPage<IEnumerable<MessageBoard.Models.Topic>>
The Topic (Details) view inherits System.Web.Mvc.ViewPage<MessageBoard.Models.TopicFormViewModel>
I'm using a TopicFormViewModel because I'm sending additional data along with the Model.
The Post (Details) view just inherits System.Web.Mvc.ViewPage<MessageBoard.Models.Post>
Now, I've created a partial view (CreatePost.ascx) which is (obviously :p) used to create a new Post. I want to be able to re-use this control on all of the views you see above.
Update
I've tried rendering the partial view using <% Html.RenderPartial("New"); %> from my Topics/Index.aspx View, but that results in an exception
The model item passed into the dictionary is of type 'System.Data.Linq.Table`1[MessageBoard.Models.Topic]', but this dictionary requires a model item of type 'MessageBoard.Models.Post'.
Now the problem is that my partial view (CreatePost.ascx) accepts a System.Web.Mvc.ViewUserControl<MessageBoard.Models.Post> and I'm not sure how to pass that from all my views above.
I'm also unsure know how to submit the .ascx values to a certain URL (i.e. /Topics/1/CreatePost), how do I tell the submit button to post to that URL?
Thanks in advance,
Marko
Ciao Marko,
Now the problem is that my partial
view (CreatePost.ascx) accepts a
System.Web.Mvc.ViewUserControl
and I'm not sure how to pass that from
all my views above.
I am not sure I understand what do you mean by "how to pass that from all my views above" but I am sure that you dont have to pass an instance of Post from your views. What is going on is that from your views you will invoke a controller action that creates the Post model object and then bind it to the CreatePost.ascx partial.
I'm also unsure know how to submit the
.ascx values to a certain URL (i.e.
/Topics/1/CreatePost), how do I tell
the submit button to post to that URL?
You have two options:
Inside your CreatePost.ascx partial you are probably using a form.
<% using (Html.BeginForm("action", "controller", FormMethod.Post, new {} )) { %>
If you use in the way I am showing you can change the first and the second params respectively to the names of the Action and the Controller that would habndle your submit.
The second option is using jQuery. Simply set an ID for your form and then
$("#myForm").submit(function(event) {
//post an ajax request to the server
});
Hope this helps!
P.S. To be able to reuse your CreatePost.ascx partial place it inside the shared view folder (where your master page is).
In regards to reusing a partial view which is not in the same view folder, use the following and pass in the model required, alternatively you can define a custom route for it.
<% html.RenderPartial("~/Views/<ControllerName>/<PartialViewName>.ascx", <model>);
#Marko
Another way would be to have in the PostController an Action like the following:
[HttpGet]
public ActionResult CreatePost( int topicId ) {
PostModel pm = _manager.CreateDefaultPost();
pm.TopicID = id;
return PartialView( "CreatePost", pm );
}
Then wherever you want to create a Post you can simply call this action that returns the strong-typed view for your new post.
Even if you have a supplementary http call over the network IMO this solution has the advantage to keep the initialization code of a new Post centralized to a single place.
From the View "point-of-view" the call to the action can be done when the user press the "New Post" button and then inject the received markup inside a modal dialog or in a place of your pleasure in the current page.
Hope it helps!
I have read a ton about this problem, but I cannot figure it out.
I have a dropdownlist on my view that is used for paging. You choose the page you want to go to and the form gets submitted. When the view is returned, the requested page is shown. The dropdownlist changes to reflect the newly shown page. This is working fine.
My problem is this:
I have 2 submit buttons, one for next page and one for prev page.
In the controller, if one of the submits have been pressed, the page# should be incremented or decremented by 1. This is happening, and the correct page of data is being shown. But, the dropdown list will not, NO MATTER WHAT I DO, reflect the new page number when the view is shown.
Basically, if the dropdown is changed by the user, it will reflect those changes when it is sent back after a form submission. If I change the page # programmatically, the dropdown will not change, even though the correct page of data gets shown.
Here is the action method:
public ActionResult Results(TechSearch search, string NextPage, string PrevPage)
{
if (NextPage != null) search.Page++;
if (PrevPage != null) search.Page--;
int resultCount = search.GetResultCount(Desk);
List<int> pages = new List<int>();
int pageCount = (int)Math.Ceiling((decimal)resultCount / search.PageSize);
for (int i = 1; i <= pageCount; i++) pages.Add(i);
ViewData["pages"] = new SelectList(pages, search.Page);
ViewData["pageCount"] = pageCount;
return View(search);
}
And here is the relevant part of the view:
<input type="submit" value="<" name="PrevPage" />
Page
<%=Html.DropDownList("Page",(SelectList)ViewData["pages"]) %>
of
<%=ViewData["pageCount"]%>
<input type="submit" value=">" name="NextPage" />
Please help.
EDIT
Page 333 of Pro ASP.NET MVC Framework by Steven Sanderson:
How Input Controls Get Their Values
Each of [the HTML Helpers for Rendering Input Controls] tries to
populate itself by looking for a value
in the following places, in this order
of priority:
ViewData.ModelState["controlName"].Value.RawValue
value parameter passed to HTML helper method, or if you’ve called an
overload that doesn’t include a value
parameter, then
ViewData.Eval("controlName")
ModelState is a temporary storage area
that ASP.NET MVC uses to retain
incoming attempted values plus binding
and validation errors. Notice that
it’s at the top of the priority list,
so its values override anything you
might set explicitly. This convention
means that you can pass an explicit
value parameter to act as the helper’s
default or initial value; but when
rerendering the view after a
validation failure, the helper will
retain any user-entered value in
preference to that default.
I have verified that ViewData.ModelState["Page"].Value.RawValue does contain the unexpected page number that is pestering me. What's the best way to work around this?
The problem was that ModelState was holding the dropdownlist's passed in value. For the HtmlHelper input controls, ModelState is given priority over explicitly set values when displaying a value in the input control.
Here is what I have done in my action method to fix the problem:
ModelState.Remove("Page");
return View(search);
I am interested in hearing other ways of fixing my problem.
Change the selected value with javascript on the click of the next or previous button, right before your postback.
Are you sure that you are guaranteed that at least one of NextPage or PrevPage will be null? (And you don't get anything weird like String.Empty forcing an increment and decrement?)
Could .LastOrDefault() be replaced with just .Last() and you are sure that you are not assigning default(int) to search.Page?
Does the below have any effect?
ViewData["pages"] = new SelectList(pages,
pages.FirstOrDefault(p => p == search.Page ));
Replacing
ViewData["pages"] = new SelectList(pages, search.Page);