MVC map to nullable bool in model - c#

With a view model containing the field:
public bool? IsDefault { get; set; }
I get an error when trying to map in the view:
<%= Html.CheckBoxFor(model => model.IsDefault) %>
Cannot implicitly convert type 'bool?' to 'bool'. An explicit conversion exists (are you missing a cast?)
I've tried casting, and using .Value and neither worked.
Note the behaviour I would like is that submitting the form should set IsDefault in the model to true or false. A value of null simply means that the model has not been populated.

The issue is you really have three possible values; true, false and null, so the the CheckBoxFor cannot handle the three states (only two states).
Brad Wilson discusses on his blog here. He uses a DropDownList for nullable booleans.
This StackOverflow question does a much better job of describing the situation than I did above. The downside to the solution is sometimes nullable does not imply false, it should be nullable. An example of this would be filter criteria where you don't want true or false applied.

If you don't care about the null value, and just want the checkbox to be unchecked when its null, you can do the following:
Create another property of type bool in your Model like this:
public bool NotNullableBool
{
get
{
return NullableBool == true;
}
set
{
NullableBool = value;
}
}
Then just use that for binding...

To me, this is a lot better:
<%= Html.CheckBox("IsDefault", Model.IsDefault.HasValue? Model.IsDefault : false) %>

Here's how to map a bullable boolean bool? property in a DropDownListFor:
#model SomeModel
<!-- ...some HTML... -->
#Html.DropDownListFor(m => m.NullableBooleanProperty, new SelectList(
new[] {
new { Value = "", Text = "-- Choose YES or NO --" },
new { Value = "true", Text = "YES" },
new { Value = "false", Text = "NO" },
},
"Value",
"Text"
))
And here's how to map it to a CheckBoxFor by using a non-nullable proxy property as a workaround:
In the ViewModel:
public bool NullableBooleanPropertyProxy
{
get
{
return NullableBooleanProperty == true;
}
set
{
NullableBooleanProperty = value;
}
}
In the View:
#model SomeModel
<!-- ...some HTML... -->
#Html.CheckBoxFor(m => m.NullableBooleanPropertyProxy)
The only downside of this workaround is that the null value will be treated as false: if you can't accept that, it's better to use a control that can support three states, such as the aforementioned DropDownListFor . 
For further info, read this post on my blog.

To add to vapcguy answer, another more "cleaner" way to do it is like follows
#Html.CheckBox("IsDefault", Model?.IsDefault)
Or
<%= Html.CheckBox("IsDefault", Model?.IsDefault) %>

You can create an editor template to render a checkbox for nullable Booleans. Name the template Boolean.cshtml to use it as the default for all of the Boolean properties within the site. Ensure that the file is in the folder ~/Views/Shared/EditorTemplates
#model bool?
#Html.CheckBox(String.Empty, Model??false)

Related

ModelState is invalid for a nullable property

I have a model where the property CompanyID allows a null value
public partial class ReportTemplateItem
{
[Key]
public int ReportTemplateItemID { get; set; }
[Required]
public int ReportTemplateID { get; set; }
public int? CompanyID { get; set; }
}
In the DBContext OnModelCreating, there is no property declared for CompanyID
But when posting the ModelState is Invalid. The form works as intended if I remove ModelState validation
ModelState Invalid
Accepting a null value appears to be the default behavior for Model Validation, what am i missing?
Razor Pages with EF Core 3.0, Nullable Reference Types is disabled
many thanks
edit - the invalid object at time of validation
In your code if you have input or select if you try to set the value with zero
that could result in constrain problems in the database
the solution is to set the value=""
and why that work and just not setting the value at all result in validation error is due to that the validation check is running against the raw value which in our case will be string "null" not real null
so just do
<select asp-for="CustomerId" asp-items="#ViewBag.CustomersList" >
<option value="">please select...</option>
</select>
that will solve the problem of wrong binding
hope MS team take care of that next .net core version
If this is any help, you may try just not to send a null value at all (exclude it from data being sent).
For example, instead of sending the folowing json data:
var data = {
reportTemplateItemID: 1,
reportTemplateID: 2,
companyID: null
};
send only:
var data = {
reportTemplateItemID: 1,
reportTemplateID: 2
};
If you have a complex object, you may easily strip all nulls before making an ajax call:
// let's remove null and undefined values
// an easy way is to serialize using a replacer function and deserialize back
const str = JSON.stringify(data, function(key, value) { return value === null ? undefined : value; });
const newData = JSON.parse(str);
See how it works:
var data = { "aaa" : undefined, "bbb": null, ccc : ""}
// newData = "{"ccc":""}"
ModelState validation won't fail in such case (as long as the value type is nullable), at least in ASP.NET Core 3.1 (I didn't check other versions).
I had similar problem when my "CustomerId" was be selected from a select element.
the problem was solved with setting value for the default option of the select:
<select asp-for="CustomerId" asp-items="#ViewBag.CustomersList" >
<option value="0">please select...</option>
</select>
before setting value="0" for the default option in my action method the ModelState.IsValid always was false although the CustomerId property in the model was nullable.

Html.DropDownListFor default SelectListItem with no value for regular bool

I have been fumbling around with this problem the entire day, so I decided to ask for some help on here, and hopefully you can help.
I have the following in a view:
#Html.DropDownListFor(model => model.IsProduction, new SelectList(new[]
{
new SelectListItem { Value = "", Text = "Select environment", Selected = true, Disabled = true },
new SelectListItem { Value = "false", Text = "Test" },
new SelectListItem { Value = "true", Text = "Production" }
},
"Value", "Text"),
new { #class = "form-control", #id = "selectEnvironment" })
As you can see, this is a dropdown for a bool with 3 values within the dropdown. The first being what I want to be selected by default (although it is not), and then the values for "true" and "false".
The bool in the model is not nullable, and is not supposed to be nullable. The form will be unable to submit, as long as either "true" or "false" has not been selected.
My question here is: Can anyone see, why it would default to always selecting the "false"-value, although I have defined the ""-value selected by default?
I have tried it without the "Disabled"-attribute, and it changes nothing.
Thanks for any help, that you may be able to give :)
Summary: This is an expected behavior of bool property which has default value of false.
I believe that you have IsProduction boolean property set like this:
[Required]
public bool IsProduction { get; set; }
Since this is non-nullable boolean property, the default value is set to false (see this reference), and any option contains false value will be selected by default when no value is assigned to the property, which overrides Selected property setting of SelectListItem instance.
If you change bool to Nullable<bool> (which you're not supposed to be), any option with Selected = true will be selected instead as shown in this fiddle because the default value for Nullable<bool> is null instead of false.
Also if you examine reference source, there are private methods named SelectInternal() and GetSelectListWithDefaultValue() which used to control behavior of DropDownListFor helper. This explains the reason why DropDownListFor with selected option placeholder must bind to bool? instead of bool.

MVC4 Set default SelectListItem in DropDownList when the list contains a SelecListItem with value 0

I am trying to set the default SelectListItem in my view to be ---Select---, with value null. This should be simple.
I know this can be done using this overload example in the view.
#Html.DropDownListFor(m=> m.SelectedId, M.List, "---Select---", null)
However, this is where I have a problem, this list I am trying to set the default on has a SelectListItem with value 0.
#Html.DropDownListFor(model => Model.Vat, new List<SelectListItem>
{
new SelectListItem{Text = "Normal 20%", Value = "20"},
new SelectListItem{Text = "Reduced 5%",Value = "5"},
new SelectListItem{Text = "Exempt", Value = "0",} //This is the cause of the problem
}, "---Select---", null)
Now what happens is when my view loads, the ---Select--- option is at the top of the list with value null, as expected, however the default selected list item is "Exempt", from what I have gathered this is because MVC sets the default dropdownlist item to whatever option has the value 0.
I have tried many different workarounds to this, such as adding
new SelectListItem{Text = "Test", Value = null, Selected = true }
but this is still not selected when the page loads, and nothing else I have tried has worked.
I cant avoid having the option Exempt with value 0, as I need this information for my application.
I have searched and searched for an answer to this but found nothing helpfull, if anyone could point me in the right direction with how to approach this I would be very grateful.
Make the property nullable:
public int? Vat { get; set;}
The reason for this is that int has the default value of 0, so when your model is bound during a post, the value of your property will get set to 0 even if the selected value is null as int is not nullable but int? is.

Using System.Web.Helpers.WebGrid

I'm trying to use a WebGrid to display data in my model and am having a huge number of issues. My model contains among other things this:
public IEnumerable<Auction> Auctions { get; set; }
What I have done is:
#{
var grid = new WebGrid(Model.Auctions, rowsPerPage: Model.PagingInfo.ItemsPerPage, defaultSort: "Title", canPage: true, canSort: true)
{SortDirection = SortDirection.Ascending};
grid.Pager(WebGridPagerModes.NextPrevious);
}
I want to display some text in the first column depending on the type of the auction in the current row, so I have written a method in the model:
public string GetAuctionType(Auction auction)
{
var type = string.Empty;
if (auction is LubAuction)
{
type = "Lowest unique wins";
}
else if (auction is EsfAuction)
{
type = "Highest wins";
}
return type;
}
Now, my view also contains:
#grid.GetHtml(
columns: grid.Columns(
grid.Column("OwnerReference", header: "Owner reference")
)
);
Question is how do I add a grid.Columns line in the above to display the text in GetAuctionType?
Also, the other issue is that there is no pager appearing and sorting does not work.
I would appreciate all help.
Thanks,
Sachin
I would move GetAuctionType logic to a Partial Class so you can access it like a normal property on each object in your collection. You may also want to take a look at question ASP.NET MVC3 WebGrid format: parameter that is covering usage of WebGrid's column format syntax.
Regarding your other issues, do you see any errors in javascript console ?

Custom dataannotations and clientside validation MVC2

I have a property on my view model that is a custom class with a value property.
e.g.
class mycustomobj
{
public int? Value {get; set; }
}
public class myviewmodel
{
[DefaultablePercentRange]
public property mycustomobj { get; set; }
}
I have a custom range attribute DefaultablePercentRange that I decorate this property with so that I can check apprpiate inputs. Associated with this is the relevant javascript validator for clientside.
The javascript is:
Sys.Mvc.ValidatorRegistry.validators["defaultablePercentRange"] = function (rule) {
var _minimum = rule.ValidationParameters["minimum"];
var _maximum = rule.ValidationParameters["maximum"];
return function (value, context) {
if (!value || !value.length) {
return true; // return true as null values allowed
}
var n = Number.parseLocale(value);
return (!isNaN(n) && _minimum <= n && n <= _maximum);
};
}
I am also using Html.EditorFor on my view with templates so that I can output the property as mycustomobj.Value rather than just mycustomobj . So the view property in html ends up being rendered something like:
<input class="defaultable tiny" default="0" defaultwhen="0" id="mycustomobj_Value" name="mycustomobj.Value" type="text" value="" placeholder="0" style="">
Now my problem is the javascript validation is passing null into my clientside validators function. After a fair amount of investigation I have identified this being because the JSON created for my custom DataAnnotationsModelValidator is not using the full id of the property. For example the JSON created is:
{"FieldName":"mycustomobj","ReplaceValidationMessageContents":true,"ValidationMessageId":"mycustomobj_validationMessage","ValidationRules":[{"ErrorMessage":"This value must be in the range 0 - 100","ValidationParameters":{"minimum":0,"maximum":100},"ValidationType":"defaultablePercentRange"},{"ErrorMessage":"This value must be in the range 0 - 100","ValidationParameters":{"minimum":0,"maximum":100},"ValidationType":"defaultablePercentRange"}]}
Where I need:
{"FieldName":"mycustomobj.value","ReplaceValidationMessageContents":true,"ValidationMessageId":"mycustomobj_value_validationMessage","ValidationRules":[{"ErrorMessage":"This value must be in the range 0 - 100","ValidationParameters":{"minimum":0,"maximum":100},"ValidationType":"defaultablePercentRange"},{"ErrorMessage":"This value must be in the range 0 - 100","ValidationParameters":{"minimum":0,"maximum":100},"ValidationType":"defaultablePercentRange"}]}
My question is. How can I get the right property name serialized out for the clientside validation so that my clientside validation will work. My serverside works just fine.
Please let me know if anyone needs more info.
I ended up getting around this by using a combination of factors.
Created a model binder specifically for mycustomobj that knows how to set the value on mycustomobj
changed template so that .Value was output but the control name was still just property name
This meant that when binding back to the viewmodel on post I can ensure the correct property on mycustomobj was set. And in the javascript the javascript client validation code was being called appropiately as the correct input id was being set.

Categories