【问题标题】:MVC Extend Html.EditorFor(int) with HTML5 min max values taken from [Range] attributeMVC 扩展 Html.EditorFor(int) 与 HTML5 最小最大值取自 [Range] 属性
【发布时间】:2018-04-02 15:14:54
【问题描述】:

我是否可以在 ASP.NET MVC5.2.2 中使用接触点来扩展为视图模型的范围受限整数属性生成的 HTML?

我在视图模型类中有一个整数属性,其范围被限制在 1 到 999 之间。我想使用 HTML5 minmax 属性,以便浏览器提供的编辑器不会让用户输入的值超出该范围。

例如在 Chrome 中,<input type="number" min="1" max="999" /> 为我提供了一个带有微调器的字段,该微调器的值限制为 1 到 999。

目前,jquery 不显眼的验证正在强制执行该规则,突出显示超出范围的值,但如果微调器首先不创建无效值会更好。

视图模型类:

public class MyViewModel
{
  [Required]
  [Range(1, 999)]
  public int? ValueInRange
  {
      get;
      set;
  }
}

用于 ValueInRange 编辑器的 cshtml:

@Html.LabelFor(m => m.ValueInRange)
@Html.EditorFor(m => m.ValueInRange)
@Html.ValidationMessageFor(m => m.ValueInRange)

生成的 HTML:

<input type="number" value="0" id="ValueInRange" name="ValueInRange"
    class="form-control text-box single-line" 
    data-val="true" 
    data-val-number="The field ValueInRange must be a number." 
    data-val-range="Please select a value between 1 and 999" 
    data-val-range-max="999" 
    data-val-range-min="1" 
    data-val-required="ValueInRange is required" />

是否有任何现有方法可以将minmax 属性添加到生成的输出中,从而获取属性的 Range 属性中声明的值?我知道我可以手动添加属性作为EditorForadditionalViewData 参数的一部分,但这是重复的,并且提供了在cshtml 和viewmodel 代码之间漂移的能力。

我宁愿不必从头开始编写 EditorFor 模板.....

【问题讨论】:

  • 不使用内置模板(您需要使用@Html.EditorFor(m =&gt; m.GroupSize, new { htmlAttributes = new { min = 1, max = 999 } }) 对其进行硬编码。您可以编写自己的扩展方法,从[Range] 读取值并添加min 和@ 987654334@ 属性,或者您可以使用 javascript/jQuery 读取data-val-* 属性并将minmax 属性添加到html
  • 我喜欢 jQuery 方法——在我看来,这个评论值得回答。谢谢。

标签: c# asp.net-mvc html


【解决方案1】:

没有开箱即用的方法,但您可以创建自己的扩展方法,或使用 javascript/jQuery 添加 minmax 属性。

扩展方法可能看起来像

public static MvcHtmlString NumericRangeInputFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
    // Add type="number"
    var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
    attributes.Add("type", "number");
    // Check for a [Range] attribute
    ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
    var property = metadata.ContainerType.GetProperty(metadata.PropertyName);
    RangeAttribute range = property.GetCustomAttributes(typeof(RangeAttribute), false).First() as RangeAttribute;
    if (range != null)
    {
        attributes.Add("min", range.Minimum);
        attributes.Add("max", range.Maximum);
    }
    return helper.TextBoxFor(expression, attributes);
}

您可以通过检查属性类型是否为数字类型(例如these answers)、为step 属性添加参数等来增强此功能。

使用jQuery,您可以检查data-val-range 属性,并根据data-val-range-mindata-val-range-max 属性添加值

var numberInputs = $('[type="number"]');
$.each(numberInputs, function(index, item){
    if ($(this).data('val-range')) {
        var min = $(this).data('val-range-min');
        var max = $(this).data('val-range-max');
        $(this).attr('min', min);
        $(this).attr('max', max);
    }
})

【讨论】:

    【解决方案2】:

    ES5 原生 js 版本:

    (function () {
        var inputNumberElements = document.querySelectorAll('input[type="number"]');
        
        Object.keys(inputNumberElements).forEach(function (key) {
            var inputNumberElement = inputNumberElements[key];
            var dataValRangeMin = inputNumberElement.getAttribute("data-val-range-min");
            var dataValRangeMax = inputNumberElement.getAttribute("data-val-range-max");
            
            if(dataValRangeMin && dataValRangeMax){
                inputNumberElement.setAttribute("min", dataValRangeMin);
                inputNumberElement.setAttribute("max", dataValRangeMax);
            }
        });    
    })();
    

    【讨论】:

      【解决方案3】:

      我已经从 .net corefx 中提取了范围验证器并创建了一个自定义的验证器,因为很难为微软人员添加两行代码。只需将其保存在某处,相应地调整命名空间并将其作为 [Html5Range()] 属性包含在您的模型中。不喜欢JS方式。

      using System;
      using System.Collections.Generic;
      using System.ComponentModel;
      using System.ComponentModel.DataAnnotations;
      using System.Globalization;
      using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
      
      namespace Admin.Validators
      {
          public class Html5RangeAttribute : ValidationAttribute, IClientModelValidator
          {
              private  string _max;
              private  string _min;
      
              private object Minimum { get;  set; }
              private object Maximum { get;  set; }
      
              private Type OperandType { get; }
              private bool ParseLimitsInInvariantCulture { get; set; }
              private bool ConvertValueInInvariantCulture { get; set; }
      
              public Html5RangeAttribute(int minimum, int maximum)
              {
                  Minimum = minimum;
                  Maximum = maximum;
                  OperandType = typeof(int);
              }
      
              public Html5RangeAttribute(double minimum, double maximum)
              {
                  Minimum = minimum;
                  Maximum = maximum;
                  OperandType = typeof(double);
              }
      
              public Html5RangeAttribute(Type type, string minimum, string maximum)
              {
                  OperandType = type;
                  Minimum = minimum;
                  Maximum = maximum;
              }       
      
              private Func<object, object> Conversion { get; set; }
      
              private void Initialize(IComparable minimum, IComparable maximum, Func<object, object> conversion)
              {
                  if (minimum.CompareTo(maximum) > 0)
                  {
                      throw new InvalidOperationException(string.Format("The maximum value '{0}' must be greater than or equal to the minimum value '{1}'.", maximum, minimum));
                  }
      
                  Minimum = minimum;
                  Maximum = maximum;
                  Conversion = conversion;
              }
      
              public override bool IsValid(object value)
              {            
                  SetupConversion();
      
                  if (value == null || (value as string)?.Length == 0)
                  {
                      return true;
                  }
      
                  object convertedValue;
      
                  try
                  {
                      convertedValue = Conversion(value);
                  }
                  catch (FormatException)
                  {
                      return false;
                  }
                  catch (InvalidCastException)
                  {
                      return false;
                  }
                  catch (NotSupportedException)
                  {
                      return false;
                  }
      
                  var min = (IComparable)Minimum;
                  var max = (IComparable)Maximum;
                  return min.CompareTo(convertedValue) <= 0 && max.CompareTo(convertedValue) >= 0;
              }
      
              public override string FormatErrorMessage(string name)
              {
                  SetupConversion();
      
                  return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, Minimum, Maximum);
              }
      
              private void SetupConversion()
              {
                  if (Conversion == null)
                  {
                      object minimum = Minimum;
                      object maximum = Maximum;
      
                      if (minimum == null || maximum == null)
                      {
                          throw new InvalidOperationException("The minimum and maximum values must be set.");
                      }
      
                      // Careful here -- OperandType could be int or double if they used the long form of the ctor.
                      // But the min and max would still be strings.  Do use the type of the min/max operands to condition
                      // the following code.
                      Type operandType = minimum.GetType();
      
                      if (operandType == typeof(int))
                      {
                          Initialize((int) minimum, (int) maximum, v => Convert.ToInt32(v, CultureInfo.InvariantCulture));
                      }
                      else if (operandType == typeof(double))
                      {
                          Initialize((double) minimum, (double) maximum,
                              v => Convert.ToDouble(v, CultureInfo.InvariantCulture));
                      }
                      else
                      {
                          Type type = OperandType;
                          if (type == null)
                          {
                              throw new InvalidOperationException("The OperandType must be set when strings are used for minimum and maximum values.");
                          }
      
                          Type comparableType = typeof(IComparable);
                          if (!comparableType.IsAssignableFrom(type))
                          {
                              throw new InvalidOperationException(string.Format("The type {0} must implement {1}.",
                                  type.FullName,
                                  comparableType.FullName));
                          }
      
                          TypeConverter converter = TypeDescriptor.GetConverter(type);
                          IComparable min = (IComparable) (ParseLimitsInInvariantCulture
                              ? converter.ConvertFromInvariantString((string) minimum)
                              : converter.ConvertFromString((string) minimum));
                          IComparable max = (IComparable) (ParseLimitsInInvariantCulture
                              ? converter.ConvertFromInvariantString((string) maximum)
                              : converter.ConvertFromString((string) maximum));
      
                          Func<object, object> conversion;
                          if (ConvertValueInInvariantCulture)
                          {
                              conversion = value => value.GetType() == type
                                  ? value
                                  : converter.ConvertFrom(null, CultureInfo.InvariantCulture, value);
                          }
                          else
                          {
                              conversion = value => value.GetType() == type ? value : converter.ConvertFrom(value);
                          }
      
                          Initialize(min, max, conversion);
                      }
                  }
              }
      
              public  void AddValidation(ClientModelValidationContext context)
              {
                  if (context == null)
                  {
                      throw new ArgumentNullException(nameof(context));
                  }
      
                  _max = Convert.ToString(Maximum, CultureInfo.InvariantCulture);
                  _min = Convert.ToString(Minimum, CultureInfo.InvariantCulture);
      
                  MergeAttribute(context.Attributes, "data-val", "false");
                  MergeAttribute(context.Attributes, "data-val-range", GetErrorMessage(context));
                  MergeAttribute(context.Attributes, "data-val-range-max", _max);
                  MergeAttribute(context.Attributes, "data-val-range-min", _min);
                  MergeAttribute(context.Attributes, "min", _min);
                  MergeAttribute(context.Attributes, "max", _max);
              }
      
              private static  bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
              {
                  if (attributes.ContainsKey(key))
                  {
                      return false;
                  }
      
                  attributes.Add(key, value);
                  return true;
              }
      
              private string GetErrorMessage(ModelValidationContextBase validationContext)
              {
                  if (validationContext == null)
                  {
                      throw new ArgumentNullException(nameof(validationContext));
                  }
      
                  return string.Format("The field {0} must be between {1} and {2}.", validationContext.ModelMetadata.GetDisplayName(), Minimum, Maximum );
              }
          }
          }
      

      【讨论】:

        【解决方案4】:

        我觉得这个方法很有效。

          @Html.EditorFor(model => model.ValueInRange, new {  
            htmlAttributes = new { 
               @class = "form-control", min="1" 
                } 
            })
        

        【讨论】:

          猜你喜欢
          • 2016-10-09
          • 2014-09-23
          • 1970-01-01
          • 2013-01-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多