为什么WebApi在模型状态中将空字符串标记为错误
本文关键字:字符串 记为 错误 WebApi 模型 状态 为什么 | 更新日期: 2023-09-27 18:06:02
我有奇怪的行为的Web API, . net 4.5.2。如果可选字符串参数为空,则ModelState没有错误。如果它不为空,也不为空,则不会再次出现错误。但如果它只是一个空字符串,我有模型状态错误。
为什么我得到它和如何禁用它?
假设应用程序服务于localhost:82
,我有这些结果:
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."
]
}
}
测试控制器和配置如下。在VS2013中,这减少到最小的默认"Asp.net web应用程序"与"WebApi"。
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是:
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:这个问题有一个变通办法,但它没有回答主要问题:为什么会发生这种情况,以及这种设计决策背后的原因是什么。
我也遇到过同样的问题,最终得出以下结论:
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();
}
}
问题根源于ModelValidationNode中的一些神奇代码,即使相应的参数具有默认值,它也会为空模型创建模型错误。上面的代码只是用TypeConverterModelBinder替换了简单类型参数的CompositeModelBinder(它调用ModelValidationNode)。
为什么我得到它和如何禁用它?
不知道你为什么得到它。这可能是你禁用它的方式,但在阅读之后,我不认为你真的想要,因为有更简单的解决方案,例如:
使用模型类以一种更简洁的方式解决了这个问题。
public class SearchModel
{
public string Q { get; set; }
}
public IHttpActionResult Search([FromUri] SearchModel model)
{
return ModelState.IsValid
? Ok(model.Q ?? "null")
: (IHttpActionResult) BadRequest(ModelState);
}
这就是为什么:
这是一个MVC特性,它将空字符串绑定到null。
我们在我们的应用程序中发现了相同的行为,并使用源代码进行深入调试
git clone https://github.com/ASP-NET-MVC/aspnetwebstack
在正确的方向上搜索是有意义的。这里的方法将空白字符串设置为null,这里的错误被添加到模型状态:
if (parentNode == null && ModelMetadata.Model == null)
{
string trueModelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey, ModelMetadata.GetDisplayName());
modelState.AddModelError(trueModelStateKey, SRResources.Validation_ValueNotFound);
return;
}
IMHO这是一个bug。但谁在乎呢。我们使用了这个变通方法
您试过[DisplayFormat(ConvertEmptyStringToNull = false)]
吗?
我们找到了另一个解决方案
public class EmptyStringToNullModelBinder : Attribute, IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.Model = string.IsNullOrWhiteSpace(valueResult?.RawValue?.ToString()) ? null : valueResult.RawValue;
return true;
}
}
对于你的情况,它应该是这样的:
[Route]
[HttpGet]
public IHttpActionResult Search([FromUri(BinderType = typeof(EmptyStringToNullModelBinder))]string q = null)
{
return this.ModelState.IsValid
? this.Ok(q ?? "null")
: (IHttpActionResult)this.BadRequest(this.ModelState);
}
下面的代码是这个答案的改编版本
public class WebApiDefaultValueBinder<T> : IModelBinder
{
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(T))
{
return false;
}
var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (val == null)
{
return false;
}
var rawValue = val.RawValue as string;
// Not supplied : /test/5
if (rawValue == null)
{
bindingContext.Model = default(T);
return true;
}
// Provided but with no value : /test/5?something=
if (rawValue == string.Empty)
{
bindingContext.Model = default(T);
return true;
}
// Provided with a value : /test/5?something=1
try
{
bindingContext.Model = (T)Convert.ChangeType(val.RawValue, typeof(T));
return true;
}
catch
{
//
}
bindingContext.ModelState.AddModelError(bindingContext.ModelName, $"Cannot convert value to {typeof(T).Name}");
return false;
}
}