i'm using the following post type:
<% using (Html.BeginForm())
{ %>
<%= Html.Hidden("EligiblePages", Model.EligiblePages) %>
.... no elements in list appear
$('.btnAction').click(function() {
$.post("Home/AddProduct", $('form').serialize(), function(retval) { $('#addProductDialog').html(retval); });
});
public class ProductViewModel
{
public List<string> EligibleProducts { get; set; }
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddProduct(string sender, ProductViewModel model)
{
...
}
i'll update this when i get home and can put together a more precise example.
and in my ViewModel that is getting posted i have a list<string> as a hidden input field. for some reason when the post occurs and i inspect the controller post method that field is not coming over correctly. any ideas?
<%
for(int i=0;i<Model.EligiblePages.Count;i++)
Html.HiddenFor(model=>model.EligiblePages[i]);
%>
This would render hidden input elements and would serealize to your model appropriately when then controller function AddProduct gets called.
Based on your View Model, I would expect your form elements to look like this:
<input type="hidden" name="EligibleProducts[0]" value="whatever" />
<input type="hidden" name="EligibleProducts[1]" value="whatever" />
etc.
That's what the default model binder is expecting.
The DefaultModelBinder will expect one or more form elements to be posted to bind to the List<string> where the name of each form element matches the name of the list property on the ViewModel. Phil Haack wrote a good blog post about various collection binding approaches.
If you have the entire collection rendered in one hidden input as seems to be suggested in your question, then what will be sent to the browser will just be one value to bind to the list and not a collection of values. You may want to enumerate over the collection and render each value in a hidden input. You could also implement your own model binder to dictate how the posted values are bound to the ViewModel, although that is probably overkill in this case.
What do you see being posted for the collection if you use a HTTP debugging proxy tool like fiddler?
Related
I have a Razor form with a list/table of items that I'd like to dynamically add items to. You can select the items from a dropdown, click "Add", and the item from the dropdown will be added to the list. I'd then like all of that to be sent via POST when I submit my form and my controller's HttpPost method can handle the input.
Is there a way to dynamically add fields and still be able to accept them as arguments in the HttpPost function?
The first answer is correct in that you can iterate over a form collection to get the values of the dynamically inserted fields within your form element. I just wanted to add that you can utilize some of the neat binding.
The code below accepts a dynamic list of textboxes that were posted against the action. Each text box in this example had the same name as dynamicField. MVC nicely binds these into an array of strings.
Full .NET Fiddle: https://dotnetfiddle.net/5ckOGu
Sample code (snippets for clarity) dynamically adding sample fields
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div id="fields"></div>
<button>Submit</button>
}
<div style="color:blue"><b>Data:</b> #ViewBag.Data</div>
<script type="text/javascript">
$(document).ready(function() {
var $fields = $('#fields');
$('#btnAddField').click(function(e) {
e.preventDefault();
$('<input type="text" name="dynamicField" /><br/>').appendTo($fields);
});
});
</script>
Sample code from the action accepting the dynamic fields in a post.
[HttpPost]
public ActionResult Index(string[] dynamicField)
{
ViewBag.Data = string.Join(",", dynamicField ?? new string[] {});
return View();
}
Screenshot of output
Every combobox/hiddenfield/textbox/... that is included inside the <form> element gets posted on submit. Doesn't really matter if you create them on-fly or have them ready by default. The biggest difference however is that with those created dynamically you can't really utilize that neat binding we're used to. You'll have to perform validation etc. manually as well.
Then you'll have a method like this:
public ActionResult HandleMyPost(FormCollection form)
{
// enumerate through the FormCollection, perform validation etc.
}
FormCollection on MSDN
I need to be able to show a dropdown list for some items in various different locations... I can safely assume that considering it is a dropdown, it will always be used in context with a form and thus a name tag will always be needed. However I wanted to know what's the best practice for this type of problem.
I have the following in a partial view and you can see I have explicitly set the name="Name" which at this moment in time is correctly going to map to the items Class property:
#model IEnumerable<TEST.Domain.Entities.James>
#{
Layout = "";
}
<select name="Name" id="JamesDropdownList">
#foreach (var item in Model)
{
<option value="#item.JamesID">#item.Name</option>
}
</select>
I may then use this partial view in such a context:
#using (Html.BeginForm("LoadInformation", "James"))
{
<div class="col-lg-9 remove-padding-left">
#{ Html.RenderAction("DropdownList", "James"); } // This is the partial view being used in a form
</div>
<div class="col-lg-3 remove-padding-right">
#Html.ContinueButton("Continue") // This is a custom button html helper I have created
</div>
}
So the question is, is this absolutely okay, to declare specifically that the name for this select field is "Name" or is there some special clever razorey way of doing this?
EDIT
In fact, don't I have to do #Html.Hidden("JamesID", item.JamesID) or something in the select? So that when I submit that form, it pushed the JamesID to the controller which looks like this:
[HttpPost]
public ViewResult LoadInformation(int jamesID)
{
// Do something with jamesID
return View("LoadInformation");
}
You can see i'm a bit confused...
Maybe a better question is, how do I have a re-usable dropdown list that I can use in a form that requires the JamesID from that dropdown list as one of the receiving controller's parameters?
One razory way would be to do this:
#Html.DropDownList("JamesDropdownList", new SelectList(Model,"JamesID","Name"))
This will assign JamesDropdownList to both the name and id attribute of the dropdown.
I have several views that have a hidden field like this:
<input type="hidden" runat="server" id="hiddenTab" value="1"/>
each view may have a different value.
I have a base controller that I want to be able to access this value on the constructor:
public class BaseController: Controller
{
public BaseController()
{
string tabid = Request.Form["hiddenTab"];
}
}
and finally I have several controllers that implement this base controller:
public class HomeController: BaseController
{
public ActionResult Index()
{
string tabid = Request.Form["hiddenTab"];
//if tabid = 1, do this.. if it is 2, do this, etc...
}
}
How can I access the hiddenfield value or the value of any other control on the view for that matter when the controller is first loading? The application is just starting at this point, so nothing has been posted yet. I have tried to access the value from both the controller and the base controller and the Request.Form has no values in it.
I am converting an application from aspx to MVC and on the aspx app, the masterpage codebehind had this:
HtmlInputHidden hidddentabid;
hidddentabid = (HtmlInputHidden)Body.FindControl("hiddenTab");
This worked fine for a master page in aspx, but not in MVC. I have spent hours looking and can't find anything on this. All I am able to find is how to access hidden values that are POSTED to a controller. Nothing on when a controller for a view is first rendered.
Any help would be greatly appreciated!
you are missing the name property for the hidden element. you can get the values based on the name. try this
<input type="hidden" id="hiddenTab" name="hiddenTab" value="1"/>
as Andrie mentioned runat="server" is not required in Asp.Net MVC
Under MVC ASP.NET use the Model, reading your code you are not using the paradigm of MVC! The best way to use MVC is to put propriety in your model, reference your model in the view and in post of your action put that model in the argument of action like
[HttpPost]
public ActionResult Index(Modelpage model){...}
the model
public class Modelpage {
public int hiddenTab {get;set;}
}
in the view
[...]
#Html.HiddenFor(m=>m.hiddenTab)
[...]
Add parameter to your action, it will be automatically mapped:
public ActionResult Index(int hiddenTab)
{
//Do smth...
}
runat="server" - this attribute is for classic ASPX pages. You don't need this if you are using ASP.NET MVC View Engine.
html controls having name attribute can only be accessed using request.form object and for this, hidden field or whatever control should be in form.
for accessing any form control in controller action method, firstly check whether the same element is included in form tag or not.
I would like to use the same view for editing a blog post and adding a blog post. However, I'm having an issue with the ID. When adding a blog post, I have no need for an ID value to be posted. When model binding binds the form values to the BlogPost object in the controller, it will auto-generate the ID in entity framework entity.
When I am editing a blog post I do need a hidden form field to store the ID in so that it accompanies the next form post. Here is the view I have right now.
<% using (Html.BeginForm("CommitEditBlogPost", "Admin"))
{ %>
<% if (Model != null)
{ %>
<%: Html.HiddenFor(x => x.Id)%>
<% } %>
Title:<br />
<%: Html.TextBoxFor(x => x.Title, new { Style = "Width: 90%;" })%>
<br />
<br />
Summary:<br />
<%: Html.TextAreaFor(x => x.Summary, new { Style = "Width: 90%; Height: 50px;" }) %>
<br />
<br />
Body:<br />
<%: Html.TextAreaFor(x => x.Body, new { Style = "Height: 250px; Width: 90%;" })%>
<br />
<br />
<input type="submit" value="Submit" />
<% } %>
Right now checking if the model is coming in NULL is a great way to know if I'm editing a blog post or adding one, because when I'm adding one it will be null as it hasn't been created yet. The problem comes in when there is an error and the entity is invalid. When the controller renders the form after an invalid model the Model != null evaluates to false, even though we are editing a post and there is clearly a model. If I render the hidden input field for ID when adding a post, I get an error stating that the ID can't be null.
Edit
I went with OJ's answer for this question, however I discovered something that made me feel silly and I wanted to share it just in case anyone was having a similar issue. The page that adds/edits blogs does not even need a hidden field for id, ever. The reason is because when I go to add a blog I do a GET to this relative URL BlogProject/Admin/AddBlogPost
This URL does not contain an ID and the action method just renders the page. The page does a POST to the same URL when adding the blog post. The incoming BlogPost entity is populated by model binding and has a null Id that will be generated by EF during save changes.
Now when editing a blog post the URL is BlogProject/Admin/EditBlogPost/{Id}. This URL contains the id of the blog post and since the page is posting back to the exact same URL the id goes with the POST to the action method that executes the edit.
The only problem I encountered with this is that the action methods cannot have identical signatures.
[HttpGet]
public ViewResult EditBlogPost(int Id)
{
}
[HttpPost]
public ViewResult EditBlogPost(int Id)
{
}
The compiler will yell at you if you try to use these two methods above. It is far too convenient that the Id will be posted back when doing a Html.BeginForm() with no arguments for action or controller. So rather than change the name of the POST method I just modified the arguments to include a FormCollection.
Like this:
[HttpPost]
public ViewResult EditBlogPost(int Id, FormCollection formCollection)
{
// You can then use formCollection as the IValueProvider for UpdateModel()
// and TryUpdateModel() if you wish. I mean, you might as well use the
// argument since you're taking it.
}
The formCollection variable is filled via model binding with the same content that Request.Form would be by default. You don't have to use this collection for UpdateModel() or TryUpdateModel() but I did just so I didn't feel like that collection was pointless since it really was just to make the method signature different from its GET counterpart.
Let me know if you find a better way to make this work. The only part I'm shaky on is taking in an unnecessary variable to make the method signature different.
A few options:
Make your Id property Nullable and check for HasValue.
Add some kind of mode indicator to your ViewData and show the Hidden field depending on the value.
Put the body of the form in a partial view and include that in two different views, one with and one without the hidden field.
What I did in my project is the following:
Have a separate action and view for editing and adding
Pass along the ID of the object in the edit URL instead of in a hidden field
Use a shared editor template for the object to avoid writing the same boilerplate code twice
If you do this correctly the add and edit views will be very small.
Something pretty weird is happening with my app:
I have the following property in my ViewModel:
public int? StakeholderId { get; set; }
It gets rendered in a partial view as follows:
<%= Html.Hidden("StakeholderId", Model.StakeholderId) %>
The form is submitted, and the relevant controller action generates an id and updates the model, before returning the same view with the updated model
The problem I'm experiencing is that the hidden field does not have anything in its "value" attribute rendered the second time even though StakeholderId now has a value.
If I just output the value on its own, it shows up on the page, so I've got it to render the value by doing this:
<input type="hidden" id="StakeholderId" name="stakeholderId" value="<%: Model.StakeholderId %>" />
But it's pretty strange that the helper doesn't pick up the updated value?
(I'm using jQuery to submit forms and render the action results into divs, but I've checked and the html I get back is already wrong before jQuery does anything with it, so I don't think that has much to do with anything)
UPDATE
I've since discovered that I can also clear the relevant ModelState key before my controller action returns the partial view.
The helper will first look for POSTed values and use them. As you are posting the form it will pick up the old value of the ID. Your workaround is correct.
ADDENDUM: Multiple HTML Forms, eg, in a Grid
As an addendeum to this issue, one thing to be VERY careful of is with multiple forms on the same page, eg, in a grid, say one generated using Ajax.BeginForm.
You might be tempted to write something along the lines of:
#foreach (var username in Model.TutorUserNames)
{
<tr>
<td>
#Html.ActionLink(username, MVC.Admin.TutorEditor.Details(username))
</td>
<td>
#using (Ajax.BeginForm("DeleteTutor", "Members",
new AjaxOptions
{
UpdateTargetId = "AdminBlock",
OnBegin = "isValidPleaseWait",
LoadingElementId = "PleaseWait"
},
new { name = "DeleteTutorForm", id = "DeleteTutorForm" }))
{
<input type="submit" value="Delete" />
#Html.Hidden("TutorName", username)
}
</td>
</tr>
}
The lethal line in here is:
#Html.Hidden("TutorName", username)
... and intend to use TutorName as your action's parameter. EG:
public virtual ActionResult DeleteTutor(string TutorName){...}
If you do this, the nasty surprise you are in for is that Html.Hidden("TutorName", username) will, as Darin Dimitrov explains, render the last POSTed value. Ie, regardless of your loop, ALL the items will be rendered with the TutorName of the last deleted Tutor!
The word around, in Razor syntax is to replace the #Html.Hidden call with an explicit input tag:
<input type="hidden" id="TutorName" name="TutorName" value='#username' />
This works as expected.
Ie:
NEVER, EVER USE Html.Hidden TO PASS A PARAMETER BACK TO YOUR ACTIONS WHEN YOU ARE USING MULTIPLE FORMS IN A GRID!!!
Final Caveat:
When constructing your hidden input tag, you need to include both name and id, set to the same value, otherwise, at the time of writing (Feb 2011) it won't work properly. Certainly not in Google Chrome. All you get is a null parameter returned if you only have an id and no name attribute.