I have strange behaviour of Web API, .Net 4.5.2. If optional string parameter is null, ModelState has no error. If it is not null and not empty, no errors again. But if it is just an empty string I have model state error.
Why do I get it and how to disable it?
Assuming app served on localhost:82
I have those results:
Url: http://localhost:82/
Response: "null"
Url: http://localhost:82/?q=1
Response: "1"
Url: http://localhost:82/?q=
Response: {
"Message": "The request is invalid.",
"ModelState": {
"q.String": [
"A value is required but was not present in the request."
]
}
}
Test controller and config is below. This is reduced to bare minimum default "Asp.net web application" with "WebApi" in VS2013.
namespace Web.Api.Test.Controllers
{
using System.Web.Http;
[Route]
public class HomeController : ApiController
{
[Route]
[HttpGet]
public IHttpActionResult Search(string q = default(string))
{
return this.ModelState.IsValid
? this.Ok(q ?? "null")
: (IHttpActionResult)this.BadRequest(this.ModelState);
}
}
}
Startup.cs is:
using Microsoft.Owin;
using WebApplication1;
[assembly: OwinStartup(typeof(Startup))]
namespace WebApplication1
{
using System.Web.Http;
using Newtonsoft.Json;
using Owin;
public class Startup
{
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configure(config =>
{
config.MapHttpAttributeRoutes();
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented;
config.Formatters.Remove(config.Formatters.XmlFormatter);
});
}
}
}
PS: This question has a workaround, but it does not answer the main question: why does this situation happen and what reasons are behind this design decision.
Inherited IvalidatableObject interface for Model class. Now, implement Validate method to write your custom rules to validate the model. Controller uses ModelState. IsValid to validate the model.
That's because an error exists; ModelState. IsValid is false if any of the properties submitted have any error messages attached to them. What all of this means is that by setting up the validation in this manner, we allow MVC to just work the way it was designed.
Sometimes strings can be empty or NULL. The difference is that NULL is used to refer to nothing. However, an empty string is used to point to a unique string with zero length.
I have had the same issue, came up with the following eventually:
public class SimpleTypeParameterBindingFactory
{
private readonly TypeConverterModelBinder converterModelBinder = new TypeConverterModelBinder();
private readonly IEnumerable<ValueProviderFactory> factories;
public SimpleTypeParameterBindingFactory(HttpConfiguration configuration)
{
factories = configuration.Services.GetValueProviderFactories();
}
public HttpParameterBinding BindOrNull(HttpParameterDescriptor descriptor)
{
return IsSimpleType(descriptor.ParameterType)
? new ModelBinderParameterBinding(descriptor, converterModelBinder, factories)
: null;
}
private static bool IsSimpleType(Type type)
{
return TypeDescriptor.GetConverter(type).CanConvertFrom(typeof (string));
}
}
public class Startup
{
public void Configure(IAppBuilder appBuilder)
{
var configuration = new HttpConfiguration();
configuration.ParameterBindingRules.Insert(0, new SimpleTypeParameterBindingFactory(configuration).BindOrNull);
configuration.EnsureInitialized();
}
}
The problem is rooted in some magic code in ModelValidationNode, which creates model errors for null models even if corresponding parameter has default value. The code above just replaces CompositeModelBinder (which calls ModelValidationNode) with TypeConverterModelBinder for simple type parameters.
Why do I get it and how to disable it?
Don't know why you get it. This maybe how you disable it, but after reading I don't think you want to really as there are simpler solutions, e.g:
Use of a model class solves this in a cleaner way.
public class SearchModel
{
public string Q { get; set; }
}
public IHttpActionResult Search([FromUri] SearchModel model)
{
return ModelState.IsValid
? Ok(model.Q ?? "null")
: (IHttpActionResult) BadRequest(ModelState);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With