ASP.NET MVC 5 renders different bool value for hidden input - asp.net-mvc

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" />

Related

Build list of data validation attributes for a given element

When using any of the Input Extension Helper Methods, like #Html.TextboxFor, any Validation Attributes from your model are automatically generated by the Razor engine (via ClientValidationEnabled/UnobtrusiveJavaScriptEnabled).
For example, take the following case which works fine
Model:
[Required]
public string QuestionOne { get; set; }
View:
#Html.TextBoxFor(model => model.QuestionOne)
#Html.ValidationMessageFor(model => model.QuestionOne)
Generated Markup:
<input type="text" id="QuestionOne" name="QuestionOne" value=""
data-val="true" data-val-required="The QuestionOne field is required." >
<span class="field-validation-valid" data-valmsg-for="QuestionOne" data-valmsg-replace="true"></span>
In this case the attributes data-val="true" & data-val-required="The QuestionOne field is required." are picked up by Unobtrusive validation and the form element is successfully validated.
However, for extensibility reasons, I want to be able to generate the <input> element myself instead of using TextBoxFor. So my view would now look like this:
<input type="textbox"
id="#Html.IdFor(m => m.QuestionTwo)"
name="#Html.NameFor(m => m.QuestionTwo)"
value="#Model.QuestionTwo"
data-val="true" data-val-required="Selection is Required" />
#Html.ValidationMessageFor(model => model.QuestionTwo)
In this case, I'm faking the validation attribute output by just re-writing data-val="true" (etc) by hand, but this would have to be expanded to cover every single case.
Here's a running Demo in .NET Fiddle
Q: Can I build /return a list of data-val-* attributes for a given element?
You can use the GetUnobtrusiveValidationAttributes() method of HtmlHelper to get the validation attributes associated with a specific property.
For example in the view
#{ var attributes = Html.GetUnobtrusiveValidationAttributes("QuestionTwo"); }
<input
type="textbox"
#foreach(var attr in attributes)
{
#:#attr.Key="#attr.Value"
}
id="#Html.IdFor(m => m.QuestionTwo)"
....
/>
Note the #:#attr.Key="#attr.Value" line will give a warning (Missing attribute name) but will run correctly
Alternatively, you could use javaScript/jQuery to add the attributes
<script type="text/javascript">
var attributes = #Html.Raw(Json.Encode(attributes));
var input = $('#QuestionTwo');
for(var i in attributes) {
input.attr(i, attributes[i]);
}
</script>
I have forked the DotNetFiddle here to show the working code for both options.
While the above code shows how it can be done, you should not be doing that. The HtmlHelper methods execute a lot of code your ignoring to ensure correct 2-way model binding, for example, the value attribute is determined by first checking for a value in ModelState, then in the ViewDataDictionary, and only if the previous values do not exist, does it use the value of the property (the second part of TextBoxFor displaying initial value, not the value updated from code explains the behavior).
Except for the incorrect value attribute, the code you have shown for the <input> is the same as will be generated by simply using #Html.TextBoxFor(m => m.Question2). I assume your real case is different, but if you cannot make use of TextBoxFor() and using an overload that accepts htmlAttributes to generate the html you need, then the correct approach is to create your own HtmlHelper method (and making use of existing methods in the HtmlHelper class and System.Web.Mvc.Html namespace)

asp.net mvc how to bind to an empty checkbox value?

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"/>

Ternary Operator in Razor View of MVC3 and Checked Attribute

I use this:
<input type="checkbox" value="#item.Id" checked="#(item.HasAccess ? "checked" : "")"/>
This worked correctly: I mean when HasAccess is true then checked="checked" and when
HasAccess is false then checked="" but always the checkboxs checked, how can I use ternary operator and handle checked attribute correctly?
Unfortunately, in razor V1, you must do it this way:
<input type="checkbox" value="#item.Id" #(item.HasAccess ? "checked=\"checked\"" : "") />
This is because in the HTML world, the mere presence of the attribute at all, regardless of the value, tells the browser to check the box.
In Razor V2, this will be less of a problem. See the conditional attributes section of the article below:
http://vibrantcode.com/blog/2012/4/10/whats-new-in-razor-v2.html/

asp.net mvc default model binding problem

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

Boolean with html helper Hidden and HiddenFor

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)
}
}

Resources