Исключение привязки аргумента запроса пустого массива ASP .Net Core - PullRequest
1 голос
/ 26 апреля 2019

У меня есть контроллер WebAPI, который выглядит следующим образом:

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    [HttpGet]
    public ActionResult<string> Get([FromQuery]DataFilter dataFilter)
    {
        return string.Join(Environment.NewLine, dataFilter?.Filter?.Select(f => f.ToString()) ?? Enumerable.Empty<string>());
    }
}

Ничего особенного, просто контроллер, который получает некоторые данные из строки запроса и выводит их в качестве ответа.Получаемый класс выглядит следующим образом:

public class DataFilter
{
    public IEnumerable<FilterType> Filter { get; set; }
}

public enum FilterType
{
    One,
    Two,
    Three,
}

Это всего лишь примеры классов, иллюстрирующих проблему, которая является ошибкой проверки при попытке вызвать этот метод следующим образом:

/api/values?filter=

И ответ:

{
  "errors": {
    "Filter": [
      "The value '' is invalid."
    ]
  },
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "80000164-0002-fa00-b63f-84710c7967bb"
}

Если я установил для моего FilterType значение NULL, это работает, но в этом случае массив просто содержит нулевые значения.И если используется так:

/api/values?filter=&filter=&filter=

Он будет просто содержать 3 нулевых значения.И я хотел, чтобы он был просто пустым или нулевым, поскольку реальные значения не передаются.

Учетная запись ASP .Net Core github содержит некоторые похожие проблемы, но сообщается, что они были исправлены в 2.2, чтоя использую.Но, возможно, они разные, или я что-то не так понимаю.

EDIT_0 : Просто чтобы показать, что я имел в виду об nullable.

Если я изменю свой класс на это:

public IEnumerable<FilterType?> Filter { get; set; } //notice that nullable is added to an Enum, not the list

Затем при вызове так:

/api/values?filter=&filter=&filter=

Я получаю 3 элемента в своем свойстве «Фильтр».Все нули.Не совсем то, что я ожидаю.Хорошо, как обходной путь, но не решение вообще.

Ответы [ 3 ]

0 голосов
/ 27 апреля 2019

Вы можете создать пользовательский механизм связывания моделей, задачей которого является удаление ошибок проверки, сгенерированных по умолчанию CollectionModelBinder. В вашем случае этого должно быть достаточно, поскольку механизм связывания моделей по умолчанию работает так, как вам нужно, и не добавляет недопустимые значения в коллекцию.

public class EmptyCollectionModelBinder : CollectionModelBinder<FilterType>
{
    public EmptyCollectionModelBinder(IModelBinder elementBinder) : base(elementBinder)
    {
    }

    public override async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        await base.BindModelAsync(bindingContext);
        //removing validation only for this collection
        bindingContext.ModelState.ClearValidationState(bindingContext.ModelName);
    }
}

Создание и регистрация модели поставщика переплетов

public class EmptyCollectionModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Metadata.ModelType == typeof(IEnumerable<FilterType>))
        {
            var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(typeof(FilterType)));

            return new EmptyCollectionModelBinder(elementBinder);
        }

        return null;
    }
}

Startup.cs

services
    .AddMvc(options =>
    {
        options.ModelBinderProviders.Insert(0, new EmptyCollectionModelBinderProvider());
    })
0 голосов
/ 04 мая 2019

Я обработал этот случай с помощью пользовательского TypeConverter и перешел в формат JSON для передачи массивов (например, filter = ["one", "two"])

Вот как я это определил:

public class JsonArrayTypeConverter<T> : TypeConverter
{
    private static readonly TypeConverter _Converter = TypeDescriptor.GetConverter(typeof(T));

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) =>
        sourceType == typeof(string) || TypeDescriptor.GetConverter(sourceType).CanConvertFrom(context, sourceType);

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        try
        {
            return JsonConvert.DeserializeObject<IEnumerable<T>>((string)value);
        }
        catch (Exception)
        {
            var dst = _Converter.ConvertFrom(context, culture, value); //in case this is not an array or something is broken, pass this element to a another converter and still return it as a list
            return new T[] { (T)dst };
        }
    }
}

И глобальная регистрация:

TypeDescriptor.AddAttributes(typeof(IEnumerable<FilterType>), new TypeConverterAttribute(typeof(JsonArrayTypeConverter<FilterType>)));

Теперь я не получаю нулевые элементы в моем списке фильтров, а также поддерживаю списки JSON с поддержкой нескольких типов (перечисления, строки, целые числа и т. Д.).).

Единственным недостатком является то, что это не будет работать с проходящими элементами, как раньше (например, filter = one & filter = two & filter = three).И не красиво выглядит строка запроса в адресной строке браузера.

0 голосов
/ 26 апреля 2019

У вас есть несколько вариантов. Вы можете создать пользовательский механизм связывания для вашего типа фильтра. Или:

Вы можете создать свой IEnumerable с помощью Nullables:

public IEnumerable<FilterType?> Filter { get; set; }

И отфильтровать нули в телефонном коде:

return string.Join(Environment.NewLine, dataFilter?.Filter?.Where(f => f != null)
   .Select(f => f.ToString()) ?? Enumerable.Empty<string>());
...