Pass empty string as nullable int property - asp.net

My model:
[DisplayName("Height")]
public int? Height { get; set; }
[DisplayName("Width")]
public int? Width { get; set; }
View:
<%= Html.TextBoxFor(x => x.Width) %>
<%= Html.TextBoxFor(x => x.Height) %>
Action:
if (ModelState.IsValid)
SaveSettings(model);
When empty strings are passed from view, the ModelState is false, but I need empty strings to be valid input, so that nulls are passed and ModelState.IsValid will be true. What can I add to the view to add this logic ? Or perhaps any other solution ? Thanks a lot for help.

There are work arounds, such as using a string property on your ViewModel and using a RegEx validator to make sure it is a number. The Regex shouldn't fire on an empty input. Then when you go from ViewModel to Model you'll have to do a conditional on the string value like
var m = new Model();
m.Property = !String.IsNullOrEmpty(this.Property)? int.parse(this.Property) as int?:null;

int? (Nullable<int>) will never be able to accept an empty string. And empty string is neither null or an int value (which are the demands of Nullable<int>).
If you want to intercept this behavior you can design your own custom model binder by either implementing IModelBinder or subclass DefaultModelBinder. Doing this, you can set an empty string value equal to null (or some magic number, but I'd prefer null).

Related

Asp.Net Core Model binding, how to get empty field to bind as a blank string?

If a form like the one below is submitted and MyField is left blank on the form, then by default Asp.Net Core model binding will place null into the corresponding property on the model as indicated below.
Example Form
<form asp-controller="SomeController" asp-action="SomeAction">
<label asp-for="MyField">My Field</label><input asp-for="MyField" type="text" />
<button type="submit">Submit</button>
</form>
Example Model
public class MyModel{
public string MyField { get; set; }
}
Example Action Method
[HttpPost]
public IActionResult Post(MyModel m) {
//m.MyField will be null if the field was left empty
//but I want it set to a blank string by the model binder
}
However, since MyField is actually transmitted in the Http Post body I'd prefer that the model binder set the MyField property on the model to a blank string rather than setting it to null. I'd prefer to reserve null for cases where MyField is not transmitted in the Http Post body. How can the model binder be changed to exhibit this behavior?
Studying the ASP.NET Core code for SimpleTypeModelBinder at https://github.com/aspnet/Mvc/blob/rel/1.1.3/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs I could see that there is a ModelMetadata.ConvertEmptyStringToNull setting which is set to true by default that is causing the blank string to be converted to null on data binding. But the property is read only so at first I couldn't figure out how to changes its value.
#rsheptolut's post on this page https://github.com/aspnet/Mvc/issues/4988 led me to a solution.
Solution:
The value has to get set at startup. This can be done via this class:
public class CustomMetadataProvider : IMetadataDetailsProvider, IDisplayMetadataProvider {
public void CreateDisplayMetadata(DisplayMetadataProviderContext context) {
if (context.Key.MetadataKind == ModelMetadataKind.Property) {
context.DisplayMetadata.ConvertEmptyStringToNull = false;
}
}
}
When it's hooked into MvcOptions in the ConfigureServices method of the startup.cs file like so
services.AddMvc()
.AddMvcOptions(options => options.ModelMetadataDetailsProviders.Add(new CustomMetadataProvider ()));
Now site wide, the default for a blank field that is posted back will be for the data binder to set the corresponding model property to a blank string rather than to null. Yea!
Have you tried making the property have a default value of empty string?
public string MyField { get; set; } = string.Empty;
an uglier solution to try is:
private string myField = string.Empty;
public string MyField
{
get { return myField ?? string.Empty; }
set { myField = value; }
}
I think it should work

Parameters in RedirectToAction don't passed down

I'm trying to pass down parameters to my Action.
There are several parameters that I need to pass down.
When debugging, I found that the simple types of parameters got their values, whereas my own class parameters is null.
return RedirectToAction("Histories", new {MyUser = user, sortOrder = "name_desc" });
And here is the Action method:
public ActionResult Histories(ApplicationUser MyUser, string sortOrder, int? page)
I did a research and found , it seems that only objects which can be serialized can be passed down.
So I simply added an annotation [Serializable] on my ApplicationUser class, and it doesn't work.
So I'm wondering what's the best practice to pass down my objects?
I certainly know I can put the MyUser into Session["CurrentUser"], but I just don't like this old fashion.
Thank you.
You have not passed int? page value, it should be like this
return RedirectToAction("Histories", new {MyUser = user,
sortOrder = "name_desc",
page =1 });
or you need to use default parameter value like this
public ActionResult Histories(ApplicationUser MyUser,
string sortOrder,
int? page = 1)

How to get the boolean value of checkbox in ASP.net MVC 4

I have a checkbox in .cshtml page;
#Html.CheckBox("All", new { #class = "chkBox"})
I want to get the boolean value of this checkbox in my controller class, for that I have tried
bool all = Convert.ToBoolean(collection["All"]);
where collection is the object of FormCollection Class.
But I am getting value of the checkbox as "all", I don't know how to get the checked or unchecked value using formcollection object.
If anyone have any Idea then please tell me. Thanks.
Why not bind it to a model?
public class MyModel {
public bool All {get; set;}
}
In your view just do the following
#html.CheckBoxFor(m=>m.All)
That should do it.
You Can Get that In this way also:
Let us assume your controller name is Index
[HttpPost]
public ActionResult Index(bool All)
{
return View();
}
If "All"(Checkbox) is Checked then All becomes true.
If "All"(Checkbox) is UNchecked then All becomes false.
Based on true or false You can modify your code in your way.
Note:The variable you mentioned in Post Controller Must be same as the name which you given for checkbox in cshtml.
i.e;
#Html.CheckBox("All", new { #class = "chkBox" })
public ActionResult Index(bool All)
That's because you input tag has the value as all. In this case, it's a good pratice to bind the right type, for sample:
public ActionResult Post(bool all)
{
//here you will get the all boolean value, if check or not as boolean
}
Or, better than this, you could create a ViewModel and bind it, for sample:
Add a class wih the fields you want to use on the Models folder.
public class MyViewModel
{
public string Name { get; set; }
public bool All { get; set; }
}
After it, in your View, you just type the View and create the controls using the Html helpers, for sample:
#model Models.MyViewModel
#using (Html.BeginForm("Post", "Home", FormMethod.Post))
{
#Html.TextBoxFor(model => model.Name)
#Html.CheckBoxFor(model => model.All)
<input type="submit" value="Send" />
}
Obs: it's just a sample
And in your controller, you just get a instance of this model, and let asp.net mvc bind it for you as the object:
public ActionResult Post(MyViewModel model)
{
// here you have the model.All as boolean value
}
It's a good pratice to do, because ViewModels represents the fields you need on the View and transfer it between contexts (controllers, views, services, apis, etc..).
You can try this approach. You can get the more information from here asp.net mvc: why is Html.CheckBox generating an additional hidden input
bool all = (collections["all"] != "false");
You need to modify your code like this. Its working: you need to first convert it in string then convert to boolean.
Convert.ToBoolean(collection["All"].ToString());
Thanks Guys for all of your answers, all make sense. But in accordance of achieving the above requirement I have modified my .cshtml code a bit, rather then using razor syntax I am using this in my .cshtml page
<input id="All" name="All" value="true" type="checkbox" class="chkBox" />
Now in my .cs page I have harvested the boolean value as follows;
all = Convert.ToBoolean(collection["All"]);
So, basically what it does is, if the checkbox is left unchecked then it will give value as false otherwise, formcollection will take into account the checkbox and will give the value of all as true.

IValidatableObject Validate method firing when DataAnnotations fails

I've a ViewModel which has some DataAnnotations validations and then for more complex validations implements IValidatableObject and uses Validate method.
The behavior I was expecting was this one: first all the DataAnnotations and then, only if there were no errors, the Validate method. How ever I find out that this isn't always true. My ViewModel (a demo one) has three fileds one string, one decimal and one decimal?. All the three properties have only Required attribute. For the string and the decimal? the behavior is the expected one, but for the decimal, when empty, Required validation fails (so far so good) and then executes the Validate method. If I inspect the property its value is zero.
What is going on here? What am I missing?
Note: I know that Required attribute is suppose to check if the value is null. So I'd expect to be told not to use Required attribute in not-nullable types (because it wont ever trigger), or, that somehow the attribute understand the POST values and note that the field wasn't filled. In the first case the attribute shouldn't trigger and the Validate method should fire. In the second case the attribute should trigger and the Validate method shouldn't fire. But my result are: the attributes triggers and the Validate method fires.
Here is the code (nothing too special):
Controller:
public ActionResult Index()
{
return View(HomeModel.LoadHome());
}
[HttpPost]
public ActionResult Index(HomeViewModel viewModel)
{
try
{
if (ModelState.IsValid)
{
HomeModel.ProcessHome(viewModel);
return RedirectToAction("Index", "Result");
}
}
catch (ApplicationException ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, "Internal error.");
}
return View(viewModel);
}
Model:
public static HomeViewModel LoadHome()
{
HomeViewModel viewModel = new HomeViewModel();
viewModel.String = string.Empty;
return viewModel;
}
public static void ProcessHome(HomeViewModel viewModel)
{
// Not relevant code
}
ViewModel:
public class HomeViewModel : IValidatableObject
{
[Required(ErrorMessage = "Required {0}")]
[Display(Name = "string")]
public string String { get; set; }
[Required(ErrorMessage = "Required {0}")]
[Display(Name = "decimal")]
public decimal Decimal { get; set; }
[Required(ErrorMessage = "Required {0}")]
[Display(Name = "decimal?")]
public decimal? DecimalNullable { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return new ValidationResult("Error from Validate method");
}
}
View:
#model MVCTest1.ViewModels.HomeViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post))
{
<div>
#Html.ValidationSummary()
</div>
<label id="lblNombre" for="Nombre">Nombre:</label>
#Html.TextBoxFor(m => m.Nombre)
<label id="lblDecimal" for="Decimal">Decimal:</label>
#Html.TextBoxFor(m => m.Decimal)
<label id="lblDecimalNullable" for="DecimalNullable">Decimal?:</label>
#Html.TextBoxFor(m => m.DecimalNullable)
<button type="submit" id="aceptar">Aceptar</button>
<button type="submit" id="superAceptar">SuperAceptar</button>
#Html.HiddenFor(m => m.Accion)
}
Considerations after comments' exchange:
The consensual and expected behavior among developers is that IValidatableObject's method Validate() is only called if no validation attributes are triggered. In short, the expected algorithm is this (taken from the previous link):
Validate property-level attributes
If any validators are invalid, abort validation returning the failure(s)
Validate the object-level attributes
If any validators are invalid, abort validation returning the failure(s)
If on the desktop framework and the object implements IValidatableObject, then call its Validate method and return any failure(s)
However, using question's code, Validate is called even after [Required] triggers. This seems an obvious MVC bug. Which is reported here.
Three possible workarounds:
There's a workaround here although with some stated problems with it's usage, apart from breaking the MVC expected behavior. With a few changes to avoid showing more than one error for the same field here is the code:
viewModel
.Validate(new ValidationContext(viewModel, null, null))
.ToList()
.ForEach(e => e.MemberNames.ToList().ForEach(m =>
{
if (ModelState[m].Errors.Count == 0)
ModelState.AddModelError(m, e.ErrorMessage);
}));
Forget IValidatableObject and use only attributes. It's clean, direct, better to handle localization and best of all its reusable among all models. Just implement ValidationAttribute for each validation you want to do. You can validate the all model or particular properties, that's up to you. Apart from the attributes available by default (DataType, Regex, Required and all that stuff) there are several libraries with the most used validations. One which implements the "missing ones" is FluentValidation.
Implement only IValidatableObject interface throwing away data annotations. This seems a reasonable option if it's a very particular model and it doesn't requires much validation. On most cases the developer will be doing all that regular and common validation (i.e. Required, etc.) which leads to code duplication on validations already implemented by default if attributes were used. There's also no re-usability.
Answer before comments:
First of all I've created a new project, from scratch with only the code you provided. It NEVER triggered both data annotations and Validate method at the same time.
Anyway, know this,
By design, MVC3 adds a [Required]attribute to non-nullable value types, like int, DateTime or, yes, decimal. So, even if you remove required attribute from that decimal it works just like it is one there.
This is debatable for its wrongness (or not) but its the way it's designed.
In you example:
'DataAnnotation' triggers if [Required] is present and no value is given. Totally understandable from my point of view
'DataAnnotation' triggers if no [Required] is present but value is non-nullable. Debatable but I tend to agree with it because if the property is non-nullable, a value must be inputted, otherwise don't show it to the user or just use a nullable decimal.
This behavior, as it seems, may be turned off with this within your Application_Start method:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I guess the property's name is self-explanatory.
Anyway, I don't understand why do you want to the user to input something not required and don't make that property nullable. If it's null then it is your job to check for it, if you don't wan't it to be null, before validation, within the controller.
public ActionResult Index(HomeViewModel viewModel)
{
// Complete values that the user may have
// not filled (all not-required / nullables)
if (viewModel.Decimal == null)
{
viewModel.Decimal = 0m;
}
// Now I can validate the model
if (ModelState.IsValid)
{
HomeModel.ProcessHome(viewModel);
return RedirectToAction("Ok");
}
}
What do you think it's wrong on this approach or shouldn't be this way?

ASP.NET MVC DropDownList Validation

I have
[DisplayName("Country")]
public List<SelectListItem> Countries { get; set; }
property in strong-type Model View class for DropDownList.
When I try to check if the ModelState.IsValid on form postback it's always false & error for Countries tells "Can't convert [value] to SelectListItem" or some of a kind.
I figured out there is no straight-forward mapping for drop down selected value (looks like I'll have to read value from Form value collection), but how can I ignore binding and validation for List property? I just want to make ModelState.IsValid attribute to be true if all the other fields are populated properly.
Thank you in advance
Finally I used workaround.
My model now:
class Model
{
...
[DisplayName("Country")]
List<Country> Countries;
Guid CountrySelected <-- new field!
...
}
I use Html.DropDownList("CountrySelected", Model.Countries.Select(x => new SelectItemList.. )
instead of HtmlDropDownListFor. HtmlDropDownListFor maps [id selected] to List not to CountrySelected property. Now [id selected] is mapped to CountrySelected
Is it because the value submitted is of type String or Country rather than a list of SelectListItem or List<SelectListItem>.
What are you binding to the control in the UI?
try
[DisplayName("Country")]
public List<Country> Countries { get; set; }
Where Country is the type name from your DAL.
EDIT:
Based on the error you are recieving, it sounds like the model is expecting the value to be a String so try swapping List<Country> for List<String>.
[DisplayName("Country")]
public List<string> Countries { get; set; }

Resources