Параметр строки привязки модели через запятую - PullRequest
23 голосов
/ 06 марта 2012

Как связать параметр строки запроса, являющийся значением, разделенным запятыми

http://localhost/Action?ids=4783,5063,5305

, с действием контроллера, ожидающим список?

public ActionResult Action(List<long> ids)
{
    return View();
}

Примечание! ids в действии контроллера должен быть список (или что-то, основанное на IEnumerable), поэтому string ids не принимается в качестве ответа, поскольку эти параметры передаются многим действиям, а разбор строки в массиве добавляет нежелательный шум.

Ответы [ 4 ]

34 голосов
/ 01 апреля 2012

Вот моя улучшенная версия решения Натана Тейлора, использованная в ответе Арчила.

  1. Связыватель Натана мог связывать только подсвойства сложных моделей, в то время как мой может также связать отдельные аргументы контроллера.
  2. Мой компоновщик также дает вам правильную обработку пустых параметров, возвращая фактический пустой экземпляр вашего массива или IEnumerable.

Чтобы подключить это, вы можете присоединить это к отдельному аргументу контроллера:

[ModelBinder(typeof(CommaSeparatedModelBinder))]

… или установите его в качестве глобального связующего по умолчанию в Application_Start в global.asax.cs:

ModelBinders.Binders.DefaultBinder = new CommaSeparatedModelBinder();

Во втором случае он попытается обработать все IEnumerables и вернется к стандартной реализации ASP.NET MVC для всего остального.

Вот:

public class CommaSeparatedModelBinder : DefaultModelBinder
{
    private static readonly MethodInfo ToArrayMethod = typeof(Enumerable).GetMethod("ToArray");

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        return BindCsv(bindingContext.ModelType, bindingContext.ModelName, bindingContext)
                ?? base.BindModel(controllerContext, bindingContext);
    }

    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
    {
        return BindCsv(propertyDescriptor.PropertyType, propertyDescriptor.Name, bindingContext)
                ?? base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }

    private object BindCsv(Type type, string name, ModelBindingContext bindingContext)
    {
        if (type.GetInterface(typeof(IEnumerable).Name) != null)
        {
            var actualValue = bindingContext.ValueProvider.GetValue(name);

            if (actualValue != null)
            {
                var valueType = type.GetElementType() ?? type.GetGenericArguments().FirstOrDefault();

                if (valueType != null && valueType.GetInterface(typeof(IConvertible).Name) != null)
                {
                    var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(valueType));

                    foreach (var splitValue in actualValue.AttemptedValue.Split(new[] { ',' }))
                    {
                            if(!String.IsNullOrWhiteSpace(splitValue))
                                list.Add(Convert.ChangeType(splitValue, valueType));
                    }

                    if (type.IsArray)
                        return ToArrayMethod.MakeGenericMethod(valueType).Invoke(this, new[] { list });
                    else
                        return list;
                }
            }
        }

        return null;
    }
}
22 голосов
/ 06 марта 2012

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

name=value&name=value2&name=value3

Чтобы использовать встроенную привязку, необходимо изменить строку запроса на

Action?ids=4783&ids=5063&ids=5305

или создать привязку пользовательской модели,Вы можете взглянуть на следующую статью (код оттуда)

public class CommaSeparatedValuesModelBinder : DefaultModelBinder
{
    private static readonly MethodInfo ToArrayMethod = typeof(Enumerable).GetMethod("ToArray");

    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
    {
        if (propertyDescriptor.PropertyType.GetInterface(typeof(IEnumerable).Name) != null)
        {
            var actualValue = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);

            if (actualValue != null && !String.IsNullOrWhiteSpace(actualValue.AttemptedValue) && actualValue.AttemptedValue.Contains(","))
            {
                var valueType = propertyDescriptor.PropertyType.GetElementType() ?? propertyDescriptor.PropertyType.GetGenericArguments().FirstOrDefault();

                if (valueType != null && valueType.GetInterface(typeof(IConvertible).Name) != null)
                {
                    var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(valueType));

                    foreach (var splitValue in actualValue.AttemptedValue.Split(new[] { ',' }))
                    {
                        list.Add(Convert.ChangeType(splitValue, valueType));
                    }

                    if (propertyDescriptor.PropertyType.IsArray)
                    {
                        return ToArrayMethod.MakeGenericMethod(valueType).Invoke(this, new[] { list });
                    }
                    else
                    {
                        return list;
                    }
                }
            }
        }

        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }
}
2 голосов
/ 07 марта 2012

Ответ Archils дал несколько идей, как реализовать мою собственную модель связующего. Мне удалось немного упростить исходный код, поскольку не было необходимости в очень общей поддержке CSV. Вместо того, чтобы устанавливать полученные данные на List<int>, я помещаю их в класс.

Связующее для модели

public class FarmModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType == typeof(FarmModel))
        {
            var newBindingContext = new ModelBindingContext()
            {
                ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
                () => CreateFarmModel(controllerContext, bindingContext),
                typeof(FarmModel)
                ),
                ModelState = bindingContext.ModelState,
                ValueProvider = bindingContext.ValueProvider
            };

            return base.BindModel(controllerContext, newBindingContext);
        }

        return base.BindModel(controllerContext, bindingContext);
    }

    private FarmModel CreateFarmModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var farmsIds = new List<int>();

        var value = bindingContext.ValueProvider.GetValue("farmData");
        if(value != null && value.AttemptedValue != null)
        {
            var array = value.AttemptedValue.Split(new [] {','});
            foreach (var s in array)
            {
                int result;
                if(int.TryParse(s, out result))
                {
                    farmsIds.Add(result);
                }
            }
        }
        return new FarmModel() { FarmIds = farmsIds };
    }
}

Модель

public class FarmModel
{
    public IEnumerable<int> FarmIds { get; set; }
}

Добавление пользовательского связующего

System.Web.Mvc.ModelBinders.Binders.Add(typeof(FarmModel), new FarmModelBinder());
1 голос
/ 19 апреля 2018

Взято из мой ответ :

Я покажу вам очень простое пользовательское связующее для моделей, которое я только что написал (и протестировал в .Net Core 2.0 ):

Моя модель переплета:

public class CustomModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        var value = valueProviderResult.FirstValue; // get the value as string

        var model = value.Split(",");
        bindingContext.Result = ModelBindingResult.Success(model);

        return Task.CompletedTask;
    }
}

Моя модель (и обратите внимание, , только у одного свойства есть моя пользовательская аннотация привязки модели ):

public class CreatePostViewModel
{
    [Display(Name = nameof(ContentText))]
    [MinLength(10, ErrorMessage = ValidationErrors.MinLength)]
    public string ContentText { get; set; }

    [BindProperty(BinderType = typeof(CustomModelBinder))]
    public IEnumerable<string> Categories { get; set; } // <<<<<< THIS IS WHAT YOU ARE INTERESTER IN

    #region View Data
    public string PageTitle { get; set; }
    public string TitlePlaceHolder { get; set; }
    #endregion
}

Что он делает: он получает некоторый текст типа «aaa, bbb, ccc», преобразует его в массив и возвращает его в ViewModel.

Надеюсь, это поможет.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я не эксперт в написании модельных папок, я узнал об этом 15 минут назад и нашел ваш вопрос (без полезного ответа), поэтому я попытался помочь. Это очень базовая модель переплета, некоторые улучшения обязательно потребуются. Я научился писать со страницы официальной документации .

...