为什么WebApi将空字符串标记为模型状态中的错误?

时间:2015-08-03 15:09:56

标签: c# asp.net-web-api

我有Web API的奇怪行为,.Net 4.5.2。如果可选字符串参数为null,则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中使用“WebApi”将其减少到最低默认值“Asp.net Web应用程序”。

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:This问题有一个解决方法,但它没有回答主要问题:为什么会出现这种情况以及这个设计决策背后的原因。

5 个答案:

答案 0 :(得分:4)

我遇到了同样的问题,最终提出了以下问题:

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中的一些魔术代码,即使相应参数具有默认值,也会为null模型创建模型错误。上面的代码只是将TypeModelBinder(它调用ModelValidationNode)替换为TypeConverterModelBinder,用于简单的类型参数。

答案 1 :(得分:2)

  

为什么我会得到它以及如何禁用它?

不知道你为什么得到它。 This也许你如何禁用它,但在阅读后我认为你不想真正有更简单的解决方案,例如:

使用模型类可以更清晰地解决这个问题。

public class SearchModel
{
    public string Q { get; set; }
}

public IHttpActionResult Search([FromUri] SearchModel model)
{
    return ModelState.IsValid
        ? Ok(model.Q ?? "null")
        : (IHttpActionResult) BadRequest(ModelState);
}

答案 2 :(得分:1)

这就是原因:

  

This is a MVC feature which binds empty strings to nulls.

我们在应用程序中发现了相同的行为,并使用源代码进行深度调试

git clone https://github.com/ASP-NET-MVC/aspnetwebstack

有意义地搜索正确的方向。将空格字符串设置为null的Here the method,并将here错误添加到模型状态:

if (parentNode == null && ModelMetadata.Model == null)
{
    string trueModelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey, ModelMetadata.GetDisplayName());
    modelState.AddModelError(trueModelStateKey, SRResources.Validation_ValueNotFound);
    return;
}

恕我直言,这是一个错误。但谁在乎。我们使用了this workaround

答案 3 :(得分:0)

您是否尝试过[DisplayFormat(ConvertEmptyStringToNull = false)]

答案 4 :(得分:0)

我们找到了另一种解决方案

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);
}