Validate parameters before model binding for PUT request in web api - asp.net-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);
}

Related

How to handle enum as string binding failure when enum value does not parse

In our ASP.net Core Web API application I am looking for a way to catch binding errors when my controller method accepts a complex object which has an ENUM property when ENUMs are de/serialized as strings.
eg.
class Person
{
public string Name {get; set;}
public SexEnum Sex {get; set;}
}
enum SexEnum
{
Male,
Female,
Other
}
We use system wide StringEnumConverter so a JSON serialized instance of Person looks like so:
{
"name": "Ann",
"sex": "female"
}
Now if I post this JSON (note the typo in the sex property):
{
"name": "Ann",
"sex": "femal"
}
the whole object received by the controller method is NULL as binding failed.
I would like to catch that binding error and, instead of having the pipeline go into the controller as if nothing is wrong, return a BAD REQUEST to the client including the detail of which property value failed to bind.
I know the type I am trying to deserialize into, I know the property type I am trying to deserialize and I can see the value does not parse into the type. So I think there must be a way of providing that detail to the client. I just don't know where and how to plug this in.
I would like the solution to be system wide so that all enums are covered, without having to put attributes on the properties of the model or on the enums themselves. (This is because we distribute our API models as a nuget package which cannot have any dependencies.)
We had this issue recently and wrote our own attribute to handle it:
public class ValidEnumValueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Type enumType = value.GetType();
bool valid = Enum.IsDefined(enumType, value);
if(!valid)
{
return new ValidationResult($"{value} is not a valid value for type {enumType.Name}");
}
return ValidationResult.Success;
}
}
class Person
{
public string Name {get; set;}
[ValidEnumValue]
public SexEnum Sex {get; set;}
}
The error is then added to the ModelState so you can use ModelState.IsValid to check if the values are valid.
if(!ModelState.IsValid)
{
return BadRequest(ModelState);
}
EDIT
If you don't want to use an attribute then you can derive a new converter from NewtonSoft StringEnumConverter and have that check the value is valid before reading the json e.g.
public class validEnumConverter : StringEnumConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if(!Enum.IsDefined(objectType, reader.Value))
{
throw new ArgumentException("Invalid enum value");
}
return base.ReadJson(reader, objectType, existingValue, serializer);
}
}
This is added to the JsonOptions in your startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new validEnumConverter());
});
}
Following up on Simply Ged's answer above, AFAICS, this actually cannot be done as the model binding exceptions are swallowed (https://github.com/aspnet/Mvc/issues/3898)
ModelState contains model binding errors and you can get some information out of that. As we currently use only JSON serialization, I ended up implementing a filter to check the ModelState errors for JsonSerializationException. It is not perfect though as eg. to get the requested value (that failed the binding) out of the JsonSerializationException you need to parse the inner exception message.
If someone finds a better solution, I will be happy to hear.
Expanding on #Simply Ged's excellent answer, 2nd part, using a nullable enum produces System.ArgumentException: 'Type provided must be an Enum.' exception. An extra step is required to handle nullable enum, or a null value for the enum:
public class validEnumConverter : StringEnumConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Type enumType = (Nullable.GetUnderlyingType(objectType) ?? objectType);
if(!Enum.IsDefined(enumType, reader.Value ?? string.Empty))
{
throw new ArgumentException("Invalid enum value");
}
return base.ReadJson(reader, objectType, existingValue, serializer);
}
}
Just got this issue recently.
The way I overcome that was applying
[EnumDataType(typeof(YOUR_ENUM_TYPE))] above your model enum.
Example:
public class SaladModel
{
[EnumDataType(typeof(SauceTypeEnum))]
public SauceTypeEnum SauceType { get; set; }
}
Now once you post this to the WebAPI endpoint, its validated and returns as BadRequest by the framework.
You do this with the JsonStringEnumConverter converter.
public class PersonModel
{
public string Name { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public SexKind Sex { get; set; }
}
public enum SexKind
{
Male,
Female,
Confused
}

ASP.NET Web API - how to pass unknown number of form-encoded POST values

The front-end of my application can send unknown number of POST values inside a form. Fro example in some cases there will be 3 values coming from certain textboxes, in some cases there will be 6 values coming from textboxes, dropdowns etc. The backend is ASP.NET Web API. I know that a simple .NET value can be passed in URI parameter to a "POST Action" using FromURI attribute and a complex type can be passed in body and fetched using FromBody attribute, in any POST Action. But in my case the number of form data values will NOT be constant rather variable and I can't use a pre-defined class to hold values using 'FromBody' attribute.
How can I tackle this situation?
You can use the FormDataCollection from the System.Net.Http.Formatting namespace.
public class ApiFormsController : ApiController
{
[HttpPost]
public IHttpActionResult PostForm(FormDataCollection form)
{
NameValueCollection items = form.ReadAsNameValueCollection();
foreach (string key in items.AllKeys)
{
string name = key;
string val = items[key];
}
return Ok();
}
}
Try to send this properties as list of properties. Make model something like this:
public class PostModel
{
public IEnumerable<PropertyModel> Properties { get; set; }
}
public class PropertyModel
{
public string Value { get; set; }
public string Source { get; set; }
// etc.
}
And action:
public IHttpActionResult Post(PostModel model)
{
//Omited
return Ok();
}

serialize null navigation property

I have simple class:
public class Person
{
public string Name {get; set;}
public Address Address {get; set;} // can be null
}
When I query Person I want webapi return me empty Address property. I tryed the following:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings = new Newtonsoft.Json.JsonSerializerSettings()
{
NullValueHandling = Newtonsoft.Json.NullValueHandling.Include,
};
but, nothing chaged, it's just not exist in result.
You don't need to configure the formatter, just add $expand query option:
GET ~/Person?$expand=Address
I tried, it works. and I'm using WebApi OData for OData v4.
{
"#odata.context":"http://jinfutan13:9123/$metadata#Albums/$entity",
"ID":5,"Name":"Name 5",
"Singer":null
}
Where Singer is also a navigation property.

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.

using MVC Model Binder, how to prevent binding inner complex object properties?

i have the following model
public class Person
{
public int Id {get;set;}
[Required()]
public string Name {get;set;}
[Required()]
public Address Address {get;set;}
}
public class Address
{
public int Id {get;set;}
[Required()]
public string City {get;set;}
[Required()]
public string Street {get;set;}
}
in the controller:
[HttpPost]
public ActionResult Create(Person entity)
{
if (ViewData.ModelState.IsValid)
{
///Some code
return this.RedirectToAction("Browse");
}
else
{
return View("Edit", ViewModel);
}
}
the problem is that the binder try to validate even the inner address class, but all i care for, is the AddressID
but the ModelBinder insist to validate even the City and Street properties.
how can i simply override the original ModelBinder just to validate the ID of the inner object (which is in my situation is AddressID)??
is there a simple way ?
It sounds like your entity and your model have two different requirements. If that is the case, then they should be two different classes. Write a separate Person and address class for MVC to bind to and don't have city or street be required.
Another possible, but less elegant solution, is to not rely on MVC doing the model binding. If you only have a handful of values that may be acceptable, but if you have a lot, then I would use my first suggestion.

Resources