MVC use Html.CheckBoxFor with nullable Bool - c#

I've got a checkbox that I want to display on my view related to a field called public, which basically says whether the particular row is public or not. In the database this is a bit field, but it allows nulls, due to how the table previously used to work.
I'm using Html.CheckBoxFor but it is complaining about this field because in the system it is not a bool type, but rather a bool? type. What I want to do is have it so that if the field value is null, then it counts as a false on the front end (unfortunately updating the database values themselves is not an option).
I have tried using the GetValueOrDefault, and putting a default value in my model file along the lines of:
public class Model
{
public bool? Public { get; set; }
public SearchModel()
{
Public = false;
}
}
however it was complaining about this, giving me the following error:
An exception of type 'System.InvalidOperationException' occurred in System.Web.Mvc.dll but was not handled in user code
Additional information: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
So i'm not sure how I can progress from here, can someone help point me in the right direction?
EDIT:
This is the code on the view that i'm trying to use to show the checkbox. In this instance i'm adding some extra html attributes so that it appears as a toggle rather than a simple checkbox:
Html.CheckBoxFor(model => model.Public, new {data_toggle = "toggle", data_off = "No", data_on = "Yes", data_size = "small"})

The specific exception you're getting occurs when you pass an expression to one of the templated helpers that can't be evaluated. Bear in mind that when you're using the expression-based helpers, you're not actually passing a property by value but rather an expression that represents a property on your model and which the helper will use to reference that property, generate field names from, etc.
You haven't shown the actual code where you're doing this, but this means essentially you can't do something like:
#Html.EditorFor(m => m.Public.GetValueOrDefault())
Because the templated helper cannot resolve that as an expression that matches up with a property on your model.
As to your actual base concern here, namely setting the value to false if it's null, you just need a custom getter and setter. #utaco's answer provides the new easier C# 6.0 method of auto-implemented properties with defaults:
public bool? Public { get; set; } = false;
For previous versions of C#, you need the following:
private bool? public;
public bool? Public
{
get { return public ?? false; }
set { public = value; }
}
However, keeping Public as a nullable bool when you have no intention of it ever actually being null just makes your code more difficult. Assuming you can change that to just bool (i.e. this is a view model and not the actual entity class tied to your database table), then you should do so. You still want to keep the private as a nullable though. That allows you accept nulls in the setter but coerce them into false values in the getter, meaning the actual value of public will always be either true or false, i.e. not null.

if you use c# 6.0 or higher you can use this:
public bool YourProp { get; set; } = false;

I've taken Chris Pratt idea but used it differently. I created a ViewModel and added a non-nullable property to update the nullable property and vice versa.
public bool? Public { get; set; }
private bool _public;
public bool _Public
{
get { return Public ?? false; }
set
{
_public = value;
Public = value;
}
}
Now in the View, you will use the non-nullable value for updating instead of the nullable value
Html.CheckBoxFor(model => model._Uploaded)
The only issue with this approach is that you will not get back null if saving changes. This worked for me as NULL value represent false in our program.

This don't need initialisation. #Html.EditorFor(m => m.AnyNullableProperty)
Below worked for me as expected.
<div class="form-group">
#Html.LabelFor(model => model.RequiresBatchNumberOnReceipt, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m => m.RequiresBatchNumberOnReceipt)
#Html.ValidationMessageFor(model => model.RequiresBatchNumberOnReceipt)
</div>
</div>

Related

MVC Range Attribute fails when a hidden field is added to the model

I have a Parent Model that contains a property used by a Sub Model for pre-filling a field on the View.
I would like to move the property into the Sub Model where it belongs, but another property's range attribute fails when i do this.
Why is the range attribute failing validation when i have a property used only to be hidden on the EditorTemplate?
The Model looks like
public class ParentModel
{
public SubModel subModel { get; set; }
}
public class SubModel
{
public uint? DefaultValue { get; set; }
[Required]
[Range(1,100)]
public uint RangedId { get; set;}
public bool EnableRange { get; set; }
}
The View (EditorTemplate) Looks like
#model SubModel
#Html.HiddenFor(model => model.DefaultValue)
#Html.TextBoxFor(model => model.RangeId)
<script>
$('#EnableRange').change(function() {
if($('#EnableRange').val()){
// remove the disabled attribute from the RangeId Field
} else {
// add the disabled attribute from the RangeId Field
}
}
</script>
The Controller Looks Like
public ActionResult Create(TViewModel model)
{
try
{
if (ModelState.IsValid)
{
//Do Something Meaningful
}
//Redisplay the view
}
}
With the DefaultValue property in the SubModel the RangeId's Range Attribute fires even when the RangeId is disabled on the form. This causes the ModelState.IsValid to be false.
When I move the DefaultValue property up to the ParentModel the Range attribute for RangeId no longer fires (because the field is disable). Which causes the ModelState.IsValid to be true, because the RangeId is never evaluated for validation.
Whatever you think is happening is NOT happening. The server side Model.IsValid does not care anything about, nor is it directly affected by disabling the control on the client side (though it can be indirectly affected as we will see below). The validation will always occur if nested form fields are posted and nested objects have required properties.
More likely, the real issue here is that when you have DefaultValue in the child model, then when you submit the model to the parent, the model binder creates an instance of SubModel because it contains a value for DefaultValue. When you move it to the parent, and you disable the RangeId, there is no value to post and therefore no SubModel gets created, and thus no validation occurs.
Ie, my guess is that when you move DefaultValue to the parent, SubModel is null on postback, thus because there is no instance to validate, there is nothing to fail validation, particularly since you are not persisting the EnableRange value.
So you really have several issues. First, disabling a control on the client will not disable validation on the server. Second, if there are no nested form fields posted to the server, then no nested objects will be created and no validation will occur (so in that respect, validation can be disabled as a side-effect if you are very careful). Third, If you DO post some nested form fields but not others, validation then nested objects WILL get created and validation will occur on fields that are not posted because they were disabled.

Readonly tag MVC depending of a condition

My model has an property whcih I assigned a ReadOnly tag. My intention is to set a propery to readonly true or false depending of a condition like
class Test {
static bool test() { // It is my model
// My logical response ...
return true;
}
[ReadOnly(test)]
datetime prop {get; set;}
}
using this model I get the error message:
Error 7 An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter
Could you, pleaee, get me an idea for this?
=====================================================================================
Solution like answer 3:
Inside Template:
cshtml:
...
#if (Model.condition) {
<td>#Html.EditorFor(m => m.prop)</td>
} else {
<td>#Html.DisplayFor(m => m.prop)</td>
}
...
It will be inside the template.
Inside Model in the copmnstructor I set the condition of the property condition:
class XX {
public condition { get; set; } // not necessary readonly, I do the logical inside the template.
public datetime prop {get; set; }
public XX (bool _condition) {
condition = _condition;
}
}
You cannot use something other than described in the error message as the parameter for attributes.
It is a sad true, but still a true.
Only this:
[ReadOnly(5)]
[ReadOnly("string")] // Or other type (int/long/byte/etc..) which can be used with const keyword.
[ReadOnly(Enums.SomeValue)]
[ReadOnly(typeof(SomeType))]
[ReadOnly(new string[] { "array", "of", "strings"} )]
So unfortunately, you wont succeed making custom parameter type:
class ReadOnlyAttribute { ReadOnlyAttribute(MyClass foo) { ... } }
One alternative would be to do it within the get/set, something like:
class test
{
static bool test() {
...
}
private datetime prop;
public datetime Prop
{
get { return prop; }
set
{
if (test)
prop = value;
else
// Ignore, throw exception, etc.
}
}
}
The metadata for the model (which includes your IsReadOnly) is created by the Model Metadata providers. This providers only have information about data types, and property names, but not about the concrete values of the properties of an instance of the model. So the metadata can not depend on the value of a property or method of the model class. (So implementing a Custom ModelMetada Provider wouldn't solve your problem).
Then, you have to find an alternative, hacky, way to do it:
Create a view model with two properties, the original, without the readonly attribute and an additional readonly property, decorated with the readonly attribute.
In the view, decide which of the two to show.
public class MyModel
{
public DateTime MyProperty { get; set;}
[ReadOnly]
public DateTime MyPropertyRo { get; set;}
}
If you want to recover the posted values, the editable version should use the original property in the Telerik control. The non-editable version should use the readonly property in the Telerik control, and the original property in a hidden-field, so that you can recover it in the post.
#if (Model.MyPropertyIsReadOnly)
{
#Html.HiddenFor(m => m.Property)
#Html.TelerikEditorFor(m => m.PropertyRo ...)
}
else
{
#Html.TelerikEditorFor(m => m.Property ...)
}
If you have to do this in many different views, you can create an Html helper (extension method for Html), which receives the 3 properties and includes the last sample code.
Finally, it would be even better to make a custom Editor template, but that's much harder to do if you don't have experience.
There is still another option: contact telerik, and ask them to implement a version of their control which receives a readonly parameter, and does this automatically for you. I think it must be really easy for them to implement it. So, if you're lucky enough...

How can I return an empty list instead of a null list from a ListBoxFor selection box in Asp.net MVC?

controller get action
IList<InputVoltage> inputVoltagesList = unitOfWorkPds.InputVoltageRepository.GetAll.ToList();
pdsEditViewModel.InputVoltageList = inputVoltagesList.Select(m => new SelectListItem { Text = m.Name, Value = m.Id.ToString() });
ViewModel
public IEnumerable<SelectListItem> InputVoltageList { get; set; }
public List<int> SelectedInputVoltages { get; set; }
View
#Html.ListBoxFor(m => m.SelectedInputVoltages, Model.InputVoltageList)
I want to receive a null list when a user makes no selections the selectedInputvoltages comes into my post controller action as null how do I get it to come in as an empty list?
I like both answers is there any benefit in using one over the other?
Either make sure it is initialized in the controller (or model/viewmodel) if null, or perhaps (ugly code though) use the coalesce operator to initialize on the fly if null:
#Html.ListBoxFor(m => m.SelectedInputVoltages, Model.InputVoltageList ?? new List<SelectListItem>())
If you initialize the list in the view model's constructor then it will always be at least an empty list. Anything which builds an instance of the view model would continue to set the list accordingly.
public class SomeViewModel
{
public List<int> SelectedInputVoltages { get; set; }
public SomeViewModel()
{
SelectedInputVoltages = new List<int>();
}
}
This way it will never be null in an instance of SomeViewModel, regardless of the view, controller, etc.
If you always want the view model's property to have a default value, then the best place to put that is in the view model. If that logic is instead placed in the controller or the view then it would need to be repeated any time you want to use it.

MVC3 format nullable DateTime in TextBoxFor without changing model

I am new to MVC3 and am still trying to pick up on the good programming practices. I had a heck of a time trying to format how a DateTime? was being displayed in my MVC3 project that doesn't have an explicit ModelName.cs file associated with the class the date was coming from.
We had a database already in place and use a .edmx (ours is called Pooling.edmx) that we get our models from. I obviously didn't want to edit the designer file to fit this widely accepted solution: Date only from TextBoxFor().
I then tried another solution which I found here: Using Html.TextBoxFor with class and custom property (MVC)
which uses:
#Html.TextBoxFor(m => m.Name, new { data_bind="value: Name", #class = "title width-7" })
This worked as I was able to use custom attributes, add class names, and set a Value all at once.
I transformed this:
#Html.TextBoxFor(m => Model.PrePoolOwner.OglDateEffective, new Dictionary<string, object> { { "class", "check-dirty input-small datePicker" }, { "data-original-value", #Model.PrePoolOwner.OglDateEffective } })
into this (which seems really ugly...and leads to me to the question):
#Html.TextBoxFor(m => Model.PrePoolOwner.OglDateEffective, new { data_original_value = Model.PrePoolOwner.OglDateEffective.HasValue ? Model.PrePoolOwner.OglDateEffective.Value.ToString("MM/dd/yyyy") : null, #class = "datePicker check-dirty", #Value = Model.PrePoolOwner.OglDateEffective.HasValue ? Model.PrePoolOwner.OglDateEffective.Value.ToString("MM/dd/yyyy") : null })
Is it better to find and use these other ways (like underscores will translate into dashes, etc) to display the information, or should I be having a ModelName.cs file to change how it is displayed at the Model level?
For some reason I feel having a huge Pooling.edmx file, that maps out our database, is limiting us now and will in the future on how we access/present/change data as the website evolves.
To get a "PrePoolOwner" object which is called up above by Model.PrePoolOwner.OglDateEffective, we have a PrePoolOwnerRow.cs file that does:
namespace OCC_Tracker.Models
{
public class PrePoolOwnerRow
{
public bool Dirty { get; set; }
public bool Delete { get; set; }
public PrePoolOwner PrePoolOwner { get; set; }
public PrePoolOwnerRow(PrePoolOwner owner)
{
this.Dirty = false;
this.Delete = false;
this.PrePoolOwner = owner;
}
public PrePoolOwnerRow()
{ }
}
}
We then call at the top of our .cshtml file
#model OCC_Tracker.Models.PrePoolOwnerRow
Ok, so a few suggestions.
First, in your example, PrePoolOwnerRow is your view model. This, in itself, is fine. But the code smell is where you expose PrePoolOwner -- a domain entity -- through your view model, PrePoolOwnerRow.
So first thing I would suggest is to update your view model to something more like this:
public class PrePoolOwnerModel
{
public bool Dirty { get; set; }
public bool Delete { get; set; }
public DateTime? OglDateEffective { get; set; }
public String OglDateEffective { get; set; }
// Other public properties here that map to properties on your PrePoolOwner entity.
}
All I did here was drop the reference to the domain model, and replace it with (a placehold comment to) the properties from your model, needed by your view.
In your controller, fetch your PrePoolOwner model, and map it to your view model using AutoMapper (this is a hypothetical example, as I don't know what your view is doing):
public ViewResult Index(int id)
{
PrePoolOwner entity = myservice.GetPrePoolOwner(id);
PrePoolOwnerModel model = Mapper.Map<PrePoolOwnerModel>(entity);
return View(model);
}
Now, to address the issue w/ the DateTime textbox, you should look at using MVC Editor Templates (this is another subject altogether, but Google it to find many topics covering the subject). This gives you more flexibility and re-usability over rendering elements of like types (i.e. DateTime).
But, aside from using that, you can add another property to your model, and use AutoMapper to set the DateTime appropriately. So, something like this in your controller, execpt you would set up a mapping in AutoMapper to handle this:
public class PrePoolOwnerModel
{
....
public String OglDateEffectiveValue { get; set; }
....
}
public ViewResult Index(int id)
{
....
model.OglDateEffectiveValue = model.OglDateEffective.HasValue ?
model.OglDateEffective.Value.ToString("MM/dd/yyyy") :
String.Empty;
....
}
Once that is set up, you can just use this new model property (OglDateEffectiveValue) for your attributes on your textbox.
I know there's a lot I covered there, but dig in and experiment with modeling your view models like this, and using AutoMapper to map the data to your view model exactly like you need it to be on your view.
Keep your view logic very simple. Avoid using anything crazy beyond the occasion loop, and maybe an if conditional here or there.

Why is my int? value being validated as if it was required?

I have an int? view model property that is validated at client-side as if it was required. That is, if I leave the field blank, it will not submit. The same does not happen for string properties.
The HTML rendered for my editor is:
<input type="text" value="" name="StatusIdSearch" id="StatusIdSearch" data-val-number="The field Status must be a number." data-val="true" class="text-box single-line">
I believe that data-val-number is causing an error because nothing is not a number, but I cannot determine why.
Any ideas?
Edit
The view-model:
public class CompromissoSearchModel
{
// other properties removed for the sake of clarity
[Display(Name = "Status")]
[EnumDataType(typeof(StatusCompromisso))]
public int? StatusIdSearch { get; set; }
// other properties removed for the sake of clarity
}
The message you are seeing it's not related to a required field validation. You're gettings this because ClientDataTypeModelValidatorProvider adds client numeric validation and it ignores if the type is nullable or nor not. You can check the code yourself:
private static IEnumerable<ModelValidator> GetValidatorsImpl(
ModelMetadata metadata,
ControllerContext context)
{
Type type = metadata.RealModelType;
if (IsNumericType(type)) {
yield return new NumericModelValidator(metadata, context);
}
}
And the IsNumericType implementation:
private static bool IsNumericType(Type type)
{
// strip off the Nullable<>
Type underlyingType = Nullable.GetUnderlyingType(type);
return _numericTypes.Contains(underlyingType ?? type);
}
Since the nullable is not considered you always get that validation. In terms of solution, you need to remove ClientDataTypeModelValidatorProvider from the used providers or maybe replace it with a custom one that does not ignore nullable.
You should be able to add the following code to your Application_Start method in Global.asax file to fix this issue:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I was having the exact same problem and managed to find a solution. None of these solutions worked for me so I thought I'd post my solution for anyone else having this problem.
The problem was not that the model binder was validating the field as invalid, but that when using TryUpdateModel the nullable property of the viewmodel wasn't nullable in the database entity.
Clearer explanation:
TryUpdateModel(dbUser, "", new[]{
"DecimalProperty"
}));
"DecimalProperty" in the viewmodel was nullable, but it wasn't nullable in dbUser.

Categories

Resources