Why Required attribute in model validation works for some but not for some other? - asp.net-web-api2

I have following Controller Post method:
[Route("")]
[ResponseType(typeof(CardPost))]
[HttpPost]
public IHttpActionResult PostCard([FromBody] CardPost CardMetaData)
{
if (!ModelState.IsValid)
{
BadRequest(ModelState);
}//Property is not caught by ModelState
if (CardMetaData.Property == 0)
{
return BadRequest();
}
//Else insert to DBContext
}
I'm trying to bind and validate data using following Model class:
class CardPost
{
[Required(ErrorMessage = "property is required")]
[JsonProperty("property")]
public int Property { get; set; }
[Required(ErrorMessage = "Card Nr. is required")]
[StringLength(6,ErrorMessage ="Card Nr. is 6 characters")]
[JsonProperty("number")]
public string Number{ get; set; }
[Required(ErrorMessage ="Card Code is required")]
[JsonProperty("code")]
public string Code{ get; set; }
[Required(ErrorMessage = "blocked is required")]
[JsonProperty("blocked")]
public bool Blocked { get; set; }
}
Required attribute works fine for Number and Code but Property and Blocked nevers throw Exception even if not included in POST request.
A workaround is to do manuel validation as above but I wonder what is behind this?
The problem is showing only in one of my Controllers.

Properties are evaluated after setting the default value, so an int with a [Required] decoration does nothing because the default value of int is 0. So even if the property does not exist on the payload, no validation will be triggered. If the property is type of string or int?, then passing null or not including the property on the payload will trigger a validation error.
Option 1 : declare int as nullable type- int? Property
Option 2 : use [BindRequired] annotation

Related

FromBody binds property with BindNever attribute

I have created a DTO:
public class MyDto :
{
[Required]
public string Name{ get; set; }
[BindNever]
public int X { get; set; }
}
When I sent body to the enpoint:
{ "Name": "name", "X": "9,}
the X is 9 instead of 0, I would expect that [FromBody] will not bind this property.
How to prevent X to be initialized when constructed and pased to a controller action?
From the documentation about [Bind], [BindRequired], [BindNever] :
These attributes affect model binding when posted form data is the source of values. They do not affect input formatters, which process posted JSON and XML request bodies.
That's why you need to add a attribute depending the serializer used. By default in ASP.NET Core, the serializer is "System.Text.Json" and the attribute expected is [JsonIgnore] :
public class MyDto :
{
[Required]
public string Name{ get; set; }
[BindNever]
[JsonIgnore]
public int X { get; set; }
}

Custom validation fires if in model class but not on page property

I have a contact Razor page implemented in ASP.NET Core 2.0. I am using model binding and custom validation.
If I use custom validation on a separate model class, the validation method is called. If I use custom validation on a property on the PageModel, the validation method is not called. However, all properties are successfully bound.
Here's the PageModel class and the separate model class:
public class ContactModel : PageModel
{
[BindProperty]
public ContactMessageModel ContactMessageModel { get; set; }
[BindProperty, CustomValidation]
public string SomeData { get; set; }
public IActionResult OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
return RedirectToPage("MessageSent");
}
}
public class ContactMessageModel
{
[Required]
public string Name { get; set; }
[Required]
public string Email { get; set; }
[Required, CustomValidation]
public string Message { get; set; }
}
A test validation attribute class is as follows:
public class CustomValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return ValidationResult.Success;
}
}
The validation attribute is called for the ContactMessageModel.Message property, but it isn't called for ContactModel.SomeData property.
Why is this and how do I fix it?
I was running into this too. In my case my issue was because I missed the addition of the [BindProperty] attribute when I moved the properties from a wrapper class model into the PageModel directly. I see that's not the case for you.
In your case I do see that you have the [Required] attribute on all properties except the ContactModel.SomeData. Maybe adding [Required] there would get things acting as you would expect?

asp.net core custom model binder just for one property

I have a simple model for my asp.net core controller:
[HttpPost]
public async Task<DefaultResponse> AddCourse([FromBody]CourseDto dto)
{
var response = await _courseService.AddCourse(dto);
return response;
}
My model is :
public class CourseDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Genre { get; set; }
public string Duration { get; set; }
public string Level { get; set; }
public string AgeRange { get; set; }
public string Notes { get; set; }
public bool Active { get; set; }
public string OrganisationCode { get; set; }
}
I'm trying to set value of "OrganisationCode" using a custom mode binder or action filter, but had no success.
I would be thnakful if you advise whats the right way to updat ethe model before executing the action.
Thanks.
I will show you here a very simple custom model binder I have just written (and tested in .Net Core 2.0):
My model binder:
public class CustomModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var value = valueProviderResult.FirstValue; // get the value as string
var model = value.Split(",");
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
My model (and notice, only one property has my custom model binder annotation):
public class CreatePostViewModel
{
[Display(Name = nameof(ContentText))]
[MinLength(10, ErrorMessage = ValidationErrors.MinLength)]
public string ContentText { get; set; }
[BindProperty(BinderType = typeof(CustomModelBinder))]
public IEnumerable<string> Categories { get; set; } // <<<<<< THIS IS WHAT YOU ARE INTERESTER IN
#region View Data
public string PageTitle { get; set; }
public string TitlePlaceHolder { get; set; }
#endregion
}
What it does is: it receives some text like "aaa,bbb,ccc", and converts it into array, and return it to the ViewModel.
I hope that helps.
DISCLAIMER: I am not an expert in model binders writing, I have learnt that 15 minutes ago, and I found your question (with no helpful answer), so I tried to help. This is a very basic model binder, some improvements are surely required. I learned how to write it from the official documentation page.
The [FromBody] attribute you are using on the action parameter. means that you direct the default behavior of Model Binding to use the formatters instead. That is why your custom Model Binder does not work.
And [FromBody] is reading the content (request body). So you won't get the request body from your Action Filter, as the request body is a non-rewindable stream, so it suppose to be read only once (I'm assuming that you are trying to read the request body from Action Filter).
My suggestion is to use your custom model binder and remove the FromBody Attribute.

check empty or invalid properties

I have a model with validation properties. On a post back to server I would like to check if model values are empty or for example dropdownlist should not be have 0 or something!
Depending on how you have created your models you could do the validation on he model properties.
Example:
public class Foo
{
public long Id { get; set; }
[Required(ErrorMessage = "Please enter name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please select a gender")]
public string SelectedGenderId{ get; set; }
}
Then in your view under your dropDownList:
#Html.ValidationMessageFor(m=> m.SelectedGenderId)
EDIT thanks to #danludwig:
Then in your controller you can validate the state of your model but doing this:
if (ModelState.IsValid)
{
// do your updates/saves
}
I hope that's is what you are after.

asp.net MVC: TryUpdateModel doesn't bind DateTime properly

I'm having issues with a date property not binding correctly using TryUpdateModel in MVC.
I am using a POCO class and a corresponding viewModel.
public class ContactModel
{
public int Id { get; set; }
[Display(Name = "First Name")]
[StringLength(50)]
[Required(ErrorMessage = "First name must be entered.")]
public string ContactGivenName { get; set; }
[Display(Name = "Last Name")]
[StringLength(50)]
[Required(ErrorMessage = "Last name must be entered.")]
public string ContactFamilyName { get; set; }
....
[Display(Name = "Date of Birth")]
public DateTime? DateOfBirth { get; set; }
}
the entity class:
public class Contact
{
[Key]
public int Id { get; set; }
[StringLength(50)]
[Column(TypeName = "varchar")]
public string ContactFamilyName { get; set; }
[StringLength(50)]
[Column(TypeName = "varchar")]
public string ContactGivenName { get; set; }
...
[Column(TypeName = "date")]
public DateTime? DateOfBirth { get; set; }
}
and in my controller :
[HttpPost]
[GridAction]
public virtual ActionResult UpdateContact(int id, FormCollection form)
{
//Find a customer whose CustomerID is equal to the id action parameter
var c = _contactService.Get(id);
if (c != null)
{
//Perform model binding (fill the customer properties and validate it).
if (TryUpdateModel(c, form))
{
The _contactService.Get returns the instance from EntityFramework.
The TryUpdateModel binds string properties like first and last name correctly, but despite a lot of tries, I can't get it to bind the date of birth.
I suspect it's a formatting issue of some kind but cannot find what it is.
When I debug, I can see in the FormCollection that the date is correctly there, with the value that was input in the view so the data is definitely passed on to the controller.
the value passed to the controller is in format :
yyyy-MM-dd
I tried modifying this on the fly with multiple format without success.
Any ideas what I'm doing wrong here ?
Thanks a lot
Well, first, I have no idea why you're using TryUpdateModel.. That's pretty archaic.
Second, it sounds to me like you have not set your culture to accept the format you expect. Add the following to your web.config if your browser is defaults already set to the correct culture:
<system.web>
<globalization culture="auto" uiculture="auto" enableclientbasedculture="true">
</globalization>
</system.web>
If you want to force a culture, then set the culture you wish in this setting.
the globalization didn't help and is actually already set, however I realized there's a custom binder for Datetime and that is probably where my problem is.
thanks