Подтвердить что ты не робот

Как заставить ASP.NET останавливать интерпретацию null как строку

У меня есть метод Web API:

public List<Task> GetTasks([FromUri] TaskFilter filter)
{

}

У метода есть параметр со списком нулевых идентификаторов:

public class TaskFilter
{
  public IList<int?> Assignees { get; set; }
}

Когда я это называю:

GET /tasks?assignees=null

Сервер возвращает ошибку:

{
  "message":"The request is invalid.",
  "modelState": {
    "assignees": [ "The value 'null' is not valid for Nullable`1." ]
  }
}

Он работает, только если я передаю пустую строку:

GET /tasks?assignees=

Но стандартные конвертеры строк запроса (из JQuery, Angular и т.д.) не работают с нулями таким образом.

Как заставить ASP.NET интерпретировать 'null' как null?

Обновление: Строка запроса может содержать несколько идентификаторов, например:

GET /tasks?assignees=1&assignees=2&assignees=null

Обновление2: JQuery преобразует нули в массив в пустые строки, а ASP.NET интерпретирует их как null. Поэтому вопрос о вызове WebAPI из Angular 1.6 ($HttpParamSerializerProvider)

Обновление3: Я знаю об обходных решениях, но я их не прошу. Я хочу решение для конкретной проблемы:

  • Это метод GET
  • Метод принимает список из Uri
  • Список может содержать null значения
  • Он должен быть List<int?>, потому что документы API генерируются автоматически, и я не хочу видеть текстовый массив как тип параметра
  • По умолчанию ASP.NET ожидает пустые строки для нулевых значений (JQuery.param работает таким образом)
  • Однако некоторые клиентские библиотеки (например, Angular) не преобразуют элементы массива null в пустые строки
4b9b3361

Ответ 1

Наконец, я нашел решение с использованием поставщика настраиваемых значений:

using System;
using System.Collections.Generic;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.ValueProviders;
using System.Web.Http.ValueProviders.Providers;
using System.Globalization;
using System.Net.Http;
using System.Web.Http.ModelBinding;

public sealed class NullableValueProviderAttribute : ModelBinderAttribute
{
    private readonly string[] _nullableColumns;

    public NullableValueProviderAttribute(params string[] nullableColumns)
    {
        _nullableColumns = nullableColumns;
    }

    public override IEnumerable<ValueProviderFactory> GetValueProviderFactories(HttpConfiguration configuration)
    {
        return new ValueProviderFactory[] { new NullableValueProviderFactory(_nullableColumns) };
    }
}

public class NullableValueProviderFactory : ValueProviderFactory, IUriValueProviderFactory
{
    private readonly string[] _nullableColumns;

    public NullableValueProviderFactory(string[] nullableColumns)
    {
        _nullableColumns = nullableColumns;
    }

    public override IValueProvider GetValueProvider(HttpActionContext actionContext)
    {
        return new NullableQueryStringValueProvider(actionContext, CultureInfo.InvariantCulture, _nullableColumns);
    }
}

public class NullableQueryStringValueProvider : NameValuePairsValueProvider
{
    private static readonly string[] _nullValues = new string[] { "null", "undefined" };

    private static IEnumerable<KeyValuePair<string, string>> GetQueryNameValuePairs(HttpRequestMessage request, string[] nullableColumns)
    {
        foreach (var pair in request.GetQueryNameValuePairs())
        {
            var isNull = Array.IndexOf(nullableColumns, pair.Key) >= 0 && Array.IndexOf(_nullValues, pair.Value) >= 0;
            yield return isNull ? new KeyValuePair<string, string>(pair.Key, "") : pair;
        };
    }

    public NullableQueryStringValueProvider(HttpActionContext actionContext, CultureInfo culture, string[] nullableColumns) :
        base(GetQueryNameValuePairs(actionContext.ControllerContext.Request, nullableColumns), culture)
    { }
}

И укажите его в действии веб-интерфейса:

public List<Task> GetTasks([NullableValueProvider("assignees")] TaskFilter filter)
{
}

Ответ 2

Вы можете создать пользовательскую привязку модели для этого конкретного типа, наследуя от DefaultModelBinder, для образца:

using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
public class TaskFilterBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext)
    {
        var request = controllerContext.HttpContext.Request;

        var assignees = request.QueryString["assignees"];

        if (assignees == "null") // check if assignees is null (string) then return NULL
            return null;
        return assignees;
    }

}

Наконец, нам нужно сообщить контроллеру о привязке, которую мы хотим использовать. Это можно указать с помощью атрибутов

[ModelBinder (TypeOf (TaskFilterBinder))]

как показано ниже:

public List<Task> GetTasks([FromUri(ModelBinder=typeof(TaskFilterBinder))] TaskFilter filter)
{
// Do your stuff.
}

Подробнее см. ссылку Пользовательские привязки моделей. Надеюсь, это решает вашу проблему. Спасибо