Как связать разделенный запятыми список в URL-адресе с массивом объектов? - PullRequest
0 голосов
/ 01 сентября 2018

У меня есть класс с именем VerseRangeReference, который имеет свойства Chapter, FirstVerse и LastVerse.

Я украсил его атрибутом TypeConverter [TypeConverter(typeof(VerseRangeReferenceConverter))]

У меня есть действие на контроллере, как этот

public Task<ViewResult> Verses(VerseRangeReference[] verses)

Но значение verses всегда является одним элементом со значением null. Вот мой конвертер типов

public class VerseRangeReferenceConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value == null)
            throw new ArgumentNullException(nameof(value));

        if (value.GetType() == typeof(string))
        {
            string source = (string)value;
            return VerseRangeReference.ParseMultiple(source);
        }
        return null;
    }
}

Результат VerseRangeReference.ParseMultiple(source) является допустимым массивом экземпляров VerseRange.

Ответы [ 2 ]

0 голосов
/ 11 февраля 2019

Вы можете использовать конвертер типов, чтобы привязать строку через запятую к последовательности значений. Однако преобразователь типов должен преобразовывать строку в последовательность напрямую. Это означает, что преобразователь типа должен быть настроен на что-то вроде IEnumerable<T> или T[]. Чтобы упростить ситуацию, я продолжу свое объяснение для IEnumerable<int>, но если вы хотите вместо этого использовать массивы, вам следует просто убедиться, что преобразователь типов преобразуется в массив вместо того, что реализует IEnumerable<T>.

Вы можете настроить преобразователь типа для IEnumerable<int>, используя TypeDescriptor.AddAttributes:

TypeDescriptor.AddAttributes(
    typeof(IEnumerable<int>),
    new TypeConverterAttribute(typeof(EnumerableIntTypeConverter)));

Это настраивает EnumerableIntTypeConverter как преобразователь типа, который может преобразовывать IEnumerable<int>.

Этот вызов должен быть сделан при запуске процесса, а в случае с ASP.NET Core это удобно сделать методом Startup.Configure.

Вот EnumerableIntTypeConverter, который преобразует разделенную запятыми строку чисел в список целых чисел:

internal class EnumerableIntTypeConverter : TypeConverter
{
    private const char Separator = ',';

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

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (!(value is string @string))
            throw new NotSupportedException($"{GetType().Name} cannot convert from {(value != null ? value.GetType().FullName : "(null)")}.");

        if (@string.Length == 0)
            return Enumerable.Empty<int>();

        var numbers = new List<int>();
        var start = 0;
        var end = GetEnd(@string, start);
        while (true)
        {
            if (!int.TryParse(
                @string.AsSpan(start, end - start),
                NumberStyles.AllowLeadingSign,
                culture,
                out var number))
                throw new FormatException($"{GetType().Name} cannot parse string with invalid format.");
            numbers.Add(number);

            if (end == @string.Length)
                break;

            start = end + 1;
            end = GetEnd(@string, start);
        }
        return numbers;
    }

    private static int GetEnd(string @string, int start)
    {
        var end = @string.IndexOf(Separator, start);
        return end >= 0 ? end : @string.Length;
    }
}

При анализе используется System.Memory, чтобы избежать выделения новой строки для каждого числа в списке. Если ваша структура не имеет перегрузки int.TryParse, которая принимает Span<char>, вы можете использовать string.Substring.

0 голосов
/ 01 сентября 2018

Мне пришлось реализовать пользовательскую модель переплета. Если кто-то может придумать, как сделать это с помощью TypeConverter, тогда я приму этот ответ, потому что связыватели моделей более сложные.

public class VerseRangeReferenceArrayModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        string modelName = bindingContext.ModelName;
        ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
        if (valueProviderResult != ValueProviderResult.None)
        {
            VerseRangeReference[] verseRangeReferences = VerseRangeReference.ParseMultiple(valueProviderResult.FirstValue);
            bindingContext.Result = ModelBindingResult.Success(verseRangeReferences);
        }
        return Task.CompletedTask;
    }
}

public class VerseRangerReferenceArrayModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType == typeof(VerseRangeReference[]))
            return new BinderTypeModelBinder(typeof(VerseRangeReferenceArrayModelBinder));
        return null;
    }
}

Это должно быть зарегистрировано.

services.AddMvc(options =>
{
    options.ModelBinderProviders.Insert(0, new VerseRangerReferenceArrayModelBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...