Invalid ModelState with null parameters - asp.net-web-api

Playing with the new WebAPI 2.0 RC1 prerelease bits... given this method:
[HttpPut("{sampleForm}/{id?}")]
public HttpResponseMessage PutSampleForm(SampleForm sampleForm, int? id)
{
if (!ModelState.IsValid)
{
// handle invalid model
}
// Insert valid model into DB with EF
return Request.CreateResponse(HttpStatusCode.OK);
}
It is marked with nullable id, but if id is in fact null, the ModelState is flagged as invalid as well... is this expected, or is there something I can do to let the ModelState know it needs to ignore nullable parameters?

yes you would use question mark on the property of your model also, like this:
private int? id {get; set;}

Related

Validate parameters before model binding for PUT request in web api

How do I distinguish between a parameter being sent as String.Empty and not being sent at all for my parameter binding for a PUT request.
My request class looks like :
public class Person
{
string name {get; set;}
int? age {get; set;}
}
My problem is with binding
When my user sends request as
{
"name":"ABC"
}
In above mentioned case age parameter is mapped as null
However when request looks like below it maps to null as well. I would like to throw a validation error in below situation.
How do I achieve it in asp net core web api
{
"name":"ABC",
"age":""
}
You should take a look at DataAnnotations.
You can add the Range attribute on your nullable int. That will only allow integers or null, not empty strings.
public class Person
{
string name {get; set;}
[Range(0,300)]
int? age {get; set;}
}
If the data annotations are not fullfilled it will set the modelstate to false
Then check the modelstate in the controller method
if (ModelState.IsValid)
{
// your logic
return new HttpResponseMessage(HttpStatusCode.OK);
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}

REST API routing standards for complex objects

For example, lets say I have the following model that is used to update a person's details via Web API:
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Are there any standards that dictate how I should structure the API routes for this endpoint when posting this data from the browser?
Would I need to have:
[Route("people/person/{personId}")]
[HttpPost]
public IHttpActionResult SavePerson(int personId, Person personDetails)
Or should I just use:
[Route("people/person")]
[HttpPost]
public IHttpActionResult SavePerson(Person personDetails)
If PersonId is 0 then it is assumed that this is new data and hence a new record will be created, otherwise an update will be performed.
BAsed on my experience the best method to post (or PUt) complex object is:
[Route("people/person")]
[HttpPost]
public IHttpActionResult SavePerson([FromBody]Person personDetails){
// then you can check here if model is valid and if id is ont set ..then call insert CRUD method or UPDATE method..something like:
if(!ModelState.IsValid) return BadRequest(ModelState);
return Ok(personDetails.Id == 0 ? _repository.insert(personDetails) : _repository.update(personDetails));
}
Hope it Help you ..
p.s other REST method for what you described is PATCH
It's not required specify the '[FromBody]' in post action. Rest api send's the data in form body only,if it is complex object.
[Route("people/person")]
[HttpPost]
public IHttpActionResult SavePerson(Person personDetails){
// then you can check here if model is valid and if id is ont set ..then call insert CRUD method or UPDATE method..something like:
if(!ModelState.IsValid) return BadRequest(ModelState);
return Ok(personDetails.Id == 0 ? _repository.insert(personDetails) : _repository.update(personDetails));
}

Web API parameters binding

I have this action in Web Api controller:
public Data GetData(ComplexModel model)
and model is
class ComplexModel
{
Guid Guid1 { get; set;}
Guid Guid2 { get; set;}
string String1 { get; set;}
}
i would like to specify custom binder for Guid type such as empty string or null would bind to empty guid and would like not use nullable type. Was trying to register model binder like this:
var pb = configuration.ParameterBindingRules;
pb.Insert(0, typeof(Guid), param => param.BindWithModelBinding(new GuidBinder()));
but it is not called and I am getting invalid model state with error message that empty string cant be converted to type Guid.
Remember, ParameterBindingRules checks the controller action parameter itself (ComplexModel in your case), not the contents of the parameter. You'd need to register a parameter binding against ComplexModel and do any processing with the custom binder to validate the model instance. Seems like you're better off making the Guid properties nullable despite your reluctance to do so.

Mvc4 RTM validation throwing an error

Has something changed with the rtm bits regarding the validation of models.
I have a simple viewmodel that looks like
public class ProductViewModel
{
[Required]
[DataMember(IsRequired = true)]
public int ProductTypeId { get; set; }
public string Product { get; set; }
}
(I just added the DataMember(IsRequired = true) as the error message I get says to use it to fix the problem. However no joy)
Within my controller the model state is telling me model is valid however when I try passing the model to my api using RestSharp I get the following error.
{"Message":"An error has occurred.","ExceptionMessage":"Property 'ProductTypeId' on type 'Mine.Model.Model' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)].","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Web.Http.Validation.Validators.ErrorModelValidator.Validate(ModelMetadata metadata, Object container)\r\n at System.Web.Http.Validation.DefaultBodyModelValidator.ShallowValidate(ModelMetadata metadata, ValidationContext validationContext, Object container)\r\n at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container)\r\n at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)\r\n at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, Object container)\r\n at System.Web.Http.Validation.DefaultBodyModelValidator.Validate(Object model, Type type, ModelMetadataProvider metadataProvider, HttpActionContext actionContext, String keyPrefix)\r\n at System.Web.Http.ModelBinding.FormatterParameterBinding.<>c_DisplayClass1.b_0(Object model)\r\n at System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass361.<>c__DisplayClass38.<Then>b__35()\r\n at System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass49.<ToAsyncVoidTask>b__48()\r\n at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func1 func, CancellationToken cancellationToken)"}
I wasnt having this problem with the rc bits but then I have only started to use the restsharp libary with the rtm bits.
Any help would be great.
In addition to adding [DataMember(IsRequired = true)] to the property, you'll also need to ensure that the attribute [DataContract] is applied at the class level.
The data entry DataContract is being consumed by the UI to create the data entry form and the back-end whenever the form is posted. So, is it safe to say that [DataMember(IsRequired = true)] is needed for the back-end and [Required(ErrorMessage = #"Product type is required)] is needed for form validation so you can access the error message?
I'm not sure why we have to do both. Why can't we have a single attribute to be used by front-end and server side?

MVC3 / EF CustomValidator two fields in model

Using MVC3 and EF4.1 how do I validate on client and server more than one field in my view model?
I have a start date text box (that can be modified) and I have the original start date in a hidden field. When the user submits the form I want to check that the modied start date is no more than one month either side of the original start date.
I can't figure out how this can be done with DataAnnotation and CustomValidation (or maybe I'm going down the wrong road)? This is an example of whay I've been working with:
[MetadataType(typeof(Metadata.MyUserMetaData))]
public partial class MyUser
{
public System.DateTime DateOfBirth { get; set; }
}
Partial Class
public class MyUserMetaData
{
[CustomValidation(typeof(AmendedStartDate), "amendedstartdate", ErrorMessage = "Invalid date."]
public DateTime StartDate { get; set; };
public DateTime OriginalStartDate { get; set; };
}
Custom Validator
public class AmendedStartDate : ValidationAttribute, IClientValidatable
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// How do I get multiple field values from object value?
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(Modelmetadata metadate, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "amendedstartdate"
};
yield return rule;
}
}
I know I've still to add jQuery to the view for this validator.
Instead of using data annotations implement IValidatableObject on your model class - it is simpler and much more clear in scenarios with cross validation.
If you still want to use ValidationAttribute you have two parameters in the IsValid method:
value represents validated value of the property where the attribute is assigned
context is context in which the property is validated. It also contains ObjectInstance and ObjectType properties to access the whole model and its type so you can cast the instance and access other properties.
The question asked in MVC custom validation: compare two dates has an example of a validator which compares to a second value in the model. That should get you started.

Resources