【问题标题】:Model binding comma separated query string parameter模型绑定逗号分隔的查询字符串参数
【发布时间】:2012-03-23 23:35:30
【问题描述】:

如何绑定逗号分隔值的查询字符串参数

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

到一个需要列表的控制器操作?

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

注意! 控制器动作中的ids 必须是一个列表(或基于IEnumerable 的东西),因此string ids 不被接受为答案,因为这些参数被传递给许多动作和解析字符串到数组会添加不需要的噪音。

【问题讨论】:

  • 您需要创建自定义模型绑定器。

标签: asp.net-mvc-3 model-binding


【解决方案1】:

默认模型绑定器要求简单类型列表采用以下格式

name=value&name=value2&name=value3

要使用内置绑定,您应该将查询字符串更改为

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

或者创建自定义模型绑定器。你可以看看following article(那里的代码)

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】:

    Archils 的回答给出了一些如何实现我自己的模型绑定器的想法。我能够稍微简化源代码,因为不需要非常通用的 CSV 支持。我没有将接收到的数据设置为List&lt;int&gt;,而是将其放到课堂上。

    模型绑定器

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

    【讨论】:

      【解决方案3】:

      这是我在 Archil 的回答中使用的 Nathan Taylor 解决方案的改进版本。

      1. Nathan 的绑定器只能绑定复杂模型的子属性, 而我的也可以绑定单独的控制器参数。
      2. 我的活页夹还可以让您正确处理空参数,方法是返回一个 数组或 IEnumerable 的实际空实例。

      要将其连接起来,您可以将其附加到单个控制器参数:

      [ModelBinder(typeof(CommaSeparatedModelBinder))]
      

      …或在 global.asax.cs 中的 Application_Start 中将其设置为全局默认绑定器:

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

      【讨论】:

      • 这个模型绑定器可以处理IEnumerable&lt;Guid&gt; 类型吗?我的实现似乎忽略了任何 Guid 绑定。
      • @pate nope,不适用于 Guid,它们不实现 IConvertible
      • 我不得不稍微修改它以与我正在使用的枚举一起使用。不确定我是否以最好的方式做到了这一点,但是您将值添加到列表的位置list.Add(Convert.ChangeType(splitValue, valueType)); 我添加了检查我的枚举类型if (valueType.Name == "Enumtype") 然后将适当的枚举添加到列表中而没有Convert.ChangeType()
      • 我要对这个类进行更新。给它一个构造函数,该构造函数接受一个字符数组并在split 方法中使用它,然后继承并使用无参数构造函数传入拆分字符以创建逗号、分号等分隔符。如果您通过@987654330 使用它@ 你可以传入你喜欢的字符列表,但是你可能会通过typeof 将其用作属性,这就是你需要继承的原因。
      • 为了进一步说明 Ben 留下的评论,我不会以这种方式处理数组,这需要对它们的名称进行硬编码。更简单和更健壮的方法是使用 valueType.IsEnum 检查属性是否为 Enum 并尝试使用 Enum.Parse 解析它,这将处理字符串名称和整数值。如果找不到枚举值,可能会进行一些错误处理?
      【解决方案4】:

      取自my answer

      我将在这里向您展示我刚刚编写的一个非常简单的自定义模型绑定器(并在 .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 分钟前就知道了,我发现了你的问题(没有有用的答案),所以我试图提供帮助。这是一个非常基本的模型绑定器,肯定需要一些改进。我从official documentation 页面学习了如何编写它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-10-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-29
        • 2011-10-25
        • 2018-03-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多