I have some problems with ASP.NET MVC’s default model binder. The View contains HTML like this:
<input name="SubDTO[0].Id" value="1" type="checkbox">
<input name="SubDTO[1].Id" value="2" type="checkbox">
This is my simplified ‘model’:
public class SubDTO
{
public virtual string Id { get; set; }
}
public class DTO
{
public List<SubDTO> SubDTOs { get; set; }
public DTO()
{
SubDTOs = new List< SubDTO>();
}
}
All this works fine if the user selects at least the first checkbox (SubDTO[0].Id). The controller ‘receives’ a nicely initialised/bound DTO. However, if the first check box is not selected but only, for example, SubDTO[1].Id the object SubDTOs is null. Can someone please explain this ‘strange’ behaviour and how to overcome it? Thanks.
Best wishes,
Christian
PS:
The controller looks like this:
[Transaction]
[AcceptVerbs(HttpVerbs.Post)]
public RedirectToRouteResult Create(DTO DTO)
{
...
}
PPS:
My problem is that if I select checkbox SubDTO[0].Id, SubDTO[1].Id, SubDTO[2].Id SubDTOs is initialised. But if I just select checkbox SubDTO[1].Id, SubDTO[2].Id (NOT the first one!!!) SubDTOs remains null. I inspected the posted values (using firebug) and they are posted!!! This must be a bug in the default model binder or might be missing something.
This behavior is "by design" in html. If a check-box is checked its value is sent to the server, if it is not checked nothing is sent. That's why you get null in your action and you'll not find value in the posted form either. The way to workaround this is to add a hidden field with the same name and some value AFTER the check-box like this:
<input name="SubDTO[0].Id" value="true" type="checkbox">
<input name="SubDTO[0].Id" value="false" type="hidden">
<input name="SubDTO[1].Id" value="true" type="checkbox">
<input name="SubDTO[1].Id" value="false" type="hidden">
In this way if you check the check-box both values will be sent but the model binder will take only the first. If the check-box is not checked only the hidden field value will be sent and you\ll get it in the action instead of null.
I think this post on Scott Hanselman's blog will explain why. The relevant line is:
The index must be zero-based and unbroken. In the above example, because there was no people[2], we stop after Abraham Lincoln and don’t continue to Thomas Jefferson.
So, in your case because the first element is not returned (as explained by others as the default behaviour for checkboxes) the entire collection is not being initialized.
Change the markup as follows:
<input name="SubDTOs" value="<%= SubDTO[0].Id %>" type="checkbox">
<input name="SubDTOs" value="<%= SubDTO[1].Id %>" type="checkbox">
What's being returned by your original markup is an unrelated set of parameters, i.e. like calling RedirectToRouteResult Create(SubDTO[0].id, SubDTO[1].id, ..., SubDTO[n].id) which is clearly not what you want, you want an array returned into your DTO object so by giving all the checkboxes the same name the return value to your function will be an array of ids.
EDIT
Try this:
<input name="SubDTO[0].Id" value="<%= SubDTO[0].Id %>" type="checkbox">
<input name="SubDTO[0].Id" value="false" type="hidden">
<input name="SubDTO[1].Id" value="<%= SubDTO[1].Id %>" type="checkbox">
<input name="SubDTO[1].Id" value="false" type="hidden">
You have to return something to make sure there is an element for each index, I suspect that any gap will cause a problem so I'd suggest using a 'null' ID, for example 0 or -1 and then process that out later in your code. Another answer would be a custom model binder.
There is always the alternate option of adding a property to your class that takes an array of strings and creates the SubDTO array from that.
public List<string> SubDTOIds
{
get { return SubDTO.Select(s=>s.Id).ToList(); }
set
{
SubDTOs = new List< SubDTO>();
foreach (string id in value)
{
SubDTOs.Add(new SubDTO { Id = id });
}
}
}
or something like that
Related
Given the following viewmodel:
public class FooViewModel
{
public bool IsBoolValue { get; set; }
}
and this view:
<input type="hidden" id="Whatever" data-something="#Model.IsBoolValue" value="#Model.IsBoolValue" />
The output of the hidden input field is this:
<input type="hidden" id="Whatever" data-something="True" value="value">
How come the value attribute is not set toTrue, but the data-something attribute is?
Is there a change in MVC 5 that would cause this, since in my MVC 4 apps this problem does not occur.
I think I've figured it out.
I believe the Razor viewengine is adhering to the HTML 5 way of setting boolean attributes, as described here:
What does it mean in HTML 5 when an attribute is a boolean attribute?
In HTML 5, a bool attribute is set like this:
<input readonly />
or
<input readonly="readonly" />
So the Razor viewengine takes your model's bool value and will render (in my case) the value attribute if Model.IsBoolValue is true. Otherwise, if it's false then the value attribute is not rendered at all.
EDIT:
As mentioned Zabavsky in the comments, to force the value of True or False to appear in the value attrbiute, simple use ToString():
<input type="hidden" value="#Model.BoolProperty.ToString()" />
<div class="field_warp_hidden">
<input type="checkbox" asp-for="ShowGoogleCaptcha" checked="#Model.ShowGoogleCaptcha" value="#Model.ShowGoogleCaptcha"/>
</div>
can set field_wrap_hidden is display:none;
checked and value must be set
I was using the hidden field in a partial view and I got an error when I used .ToString()
alternative option was to specify the value property explicitly even after specifying asp-for
<input type="hidden" value="#Model.TargetmarketsToChangeAvailability[i].Available" asp-for="TargetmarketsToChangeAvailability[i].Available" />
I have a scaffold list type table in my view. Above to the table, I have two checkboxes also. When I click on Edit link I need to pass the selected Id as well as the CheckBoxes' value also.
I can use ActionLink to pass the primary key value like
#Html.ActionLink("Action","Controller", new { id=#item.Id })
I can get the CheckBoxes Value by wrapping them into a Html.Beginform like,
#using(Html.BeginForm("Action","Controller",FormMethod="Post"){
<input type="checkbox" name="Check" value="1" />
<input type="checkbox" name="Check" value="2" />
-- table
<input type="Submit" value="Edit"/>
and in my controller, I can handle this like
[HttpPost]
Public ActionResult Edit(IEnumerable<string> check)
{ }
Here, I need to get both the Primary key value as well as Checkboxes' value, I tried in these two ways, and I could get any one of these only. Can anyone help me to get both the values? Thanks in advance.
You can include id in the action parameter. Like
[HttpPost]
Public ActionResult Edit(IEnumerable<string> check, int id)
{ }
Also, you will need to post the form using a submit button not the action link.
So, for sending the id, you will need to put it in a hidden field and it will automatically be sent on post.
Make sure you name the hidden field the same as the parameter name i.e. "id".
Edit
Do like this:
Take a hidden field in form: like this:
<input type="hidden" id="hf" value="TEst" name="hid" />
Then take a edit button in each row and make it call a javascript function. Like below:
<button type="button" onclick="clickfunc(#item.UniqueId)">Edit</button>
Next Come in Javascript and set hidden field and then do a form submit. Like below:
#section scripts{
<script type="text/javascript">
function clickfunc(id) {
$("#hf").val(id);
$('form').submit();
}
</script>
}
Now you get the values in your controller action. In my example it looks like:
public ActionResult EditTry(string hid)
{
return View();
}
You will get selected value in "check" string
To get the name type jquery on submit button click and store it in hidden field and access the hidden field from form collection
var getval = $("#Check option:selected").text();
$("#hdnText").val(getval);
This is controller Code:
[HttpPost]
Public ActionResult Edit(string check, FormCollection collection)
{
string strText = collection["hdnText"].ToString();
string strValue = check;
}
I am modifying an existing asp.net mvc application that creates a list of checkboxes from a list on the model, with property name "MyModelProperty" and additionally generates one more input element for "Select All" which has the following html:
<input name="MyModelProperty_SelectAll" type="checkbox" CHECKED="checked" value=""/>
What is the property declaration in the model that would create a boolean property that would bind to this existing element in the view?
I tried, 'public bool MyModelProperty_SelectAll {get;set;}' but it was returning null. Is that because the value is an empty string in the html input control?
Change your Model property for string:
public string MyModelProperty_SelectAll { get; set; }
Set some value for the checkbox, then in the server, if its checked, the value will be the given value, else you will see null.
<input name="MyModelProperty_SelectAll" type="checkbox" value="all"/>
EDIT:
If you want to bind this to a bool, you must provide a value="true" and a hidden field:
<input class="input-validation-error" id="MyModelProperty_SelectAll" name="MyModelProperty_SelectAll" type="checkbox" value="true">
<input name="MyModelProperty_SelectAll" type="hidden" value="false">
This example code above was generated using the Html.CheckBox helper.
If you don't use MVC htmlhelpers for generating checkboxes you must add an additional hidden element for your checkbox:
<input name="MyModelProperty_SelectAll" type="checkbox" value="true"/>
<input name="MyModelProperty_SelectAll" type="hidden" value="true"/>
What's up with this? The viewmodel variable is a bool with value true.
<%= Html.HiddenFor(m => m.TheBool) %>
<%= Html.Hidden("IsTimeExpanded",Model.TheBool) %>
<input type="hidden" value="<%=Model.TheBool%>" name="TheBool" id="TheBool">
Results in:
<input id="TheBool" name="TheBool" value="False" type="hidden">
<input id="TheBool" name="TheBool" value="False" type="hidden">
<input value="True" name="TheBool" id="TheBool" type="hidden">
What am I doing wrong? Why don't the helpers work as intended?
1) use different (unique) ids
2) don't use this helper, use
<input type="hidden" name="the-name"
value="<%= Html.AttributeEncode(Model.TheBool) %>" id="TheBool_1216786" />
As answered here the problem is that HTML helpers by default use the posted values (if available) then refer to the model. Personally I don't think this makes a whole bunch of sense and now wonder how many other bugs lie in wait throughout our platform.
Anyway, the solution posted in the aforementioned answer will solve the problem, just add this line before you return from the controller:
ModelState.Remove("TheBool")
And yes, it's a bit rubbish because you can only use a string reference... but it does work.
Here's an example in razor:
html:
#Html.HiddenFor(x => Model.TheBool, new { #id = "hdnBool" })
javascript:
alert($('#hdnBool').val());
model:
public class MyModel()
{
public bool TheBool{ get; set; }
}
I had similar and ended up getting round it like this.
The situation is the user wants a Save and then confirm save scenario....
I chose to use the solution below rather than
ModelSate.Remove("OperationConfirmed");
(which does work) as I feel it is more intuative....
#{
string btnSaveCaption = "Save Changes";
if (Model.OperationConfirmed)
{
btnSaveCaption = "Confirm Save Changes";
#Html.Hidden("OperationConfirmed", true)
}
}
Consider the following code:
public ActionResult Edit(int id)
{
return View(db.Foos.Single(x => x.Id == id));
}
When user submits the changes, I would like to receive both original and current object values, such that the Update code can be:
Foo foo = db.Foos.Attach(current, original);
db.SubmitChanges();
I see two options:
1) Render a number of hidden inputs containing original values
<input type="hidden" name="original.A" value="<%= Model.A %> />
<input type="hidden" name="original.B" value="<%= Model.B %> />
<input type="text" name="current.A" value="<%= Model.A %>
<input type="text" name="current.B" value="<%= Model.B %>
and submit to:
public ActionResult Update(Foo current, Foo original)
{
Foo foo = db.Foos.Attach(current, original);
db.SubmitChanges();
}
2) Use some serialization/deserialization into one hidden field
<input type="hidden" name="original" value="<%= Serialize(original) %> />
and sumbmit to:
public ActionResult Update(Foo current, string original)
{
Foo original = DeserializeFrom<Foo>(original);
Foo foo = db.Foos.Attach(current, original);
db.SubmitChanges();
}
Are there any other options? Or tools that make writing such code easier?
EDIT:
To be more clear... the idea of keeping original value is to eliminate extra select that happens if code written this way:
public ActionResult Update(Foo changed)
{
Foo original = db.Foos.Single(x => x.Id == changed.Id);
MyUtils.CopyProps(original, current);
db.SubmitChanges();
}
make some custom HtmlHelper extension methods that simply write out both the hidden and textbox element. That way, your view markup stays simple, but you still get the pre/post state tracking in your post info.
I would stray away from the serialization option :-/
While I wouldn't know how to solve your problem, I can tell you that what you're thinking of would be extremely unsafe. In fact nothing would stop the client from altering the data sent through the request and in the best case have invalid data entered in your database. You should not trust the client with hidden fields, querystrings or cookies containing data you have to insert (unless you sign the data sent to the client in the first place and check the signature later).