37

次のビューモデルがあるとします。

public class SomeViewModel
{
  public bool IsA { get; set; }
  public bool IsB { get; set; }
  public bool IsC { get; set; } 
  //... other properties
}

利用可能なプロパティの少なくとも 1 つが true であることを検証するカスタム属性を作成したいと考えています。プロパティに属性をアタッチし、次のようにグループ名を割り当てることができると思います。

public class SomeViewModel
{
  [RequireAtLeastOneOfGroup("Group1")]
  public bool IsA { get; set; }

  [RequireAtLeastOneOfGroup("Group1")]
  public bool IsB { get; set; }

  [RequireAtLeastOneOfGroup("Group1")]
  public bool IsC { get; set; } 

  //... other properties

  [RequireAtLeastOneOfGroup("Group2")]
  public bool IsY { get; set; }

  [RequireAtLeastOneOfGroup("Group2")]
  public bool IsZ { get; set; }
}

フォームの値が変更されるため、フォームを送信する前にクライアント側で検証したいので、可能であればクラスレベルの属性を避けたいと思います。

これには、カスタム属性のパラメーターとして渡された同一のグループ名値を持つすべてのプロパティを見つけるために、サーバー側とクライアント側の両方の検証が必要になります。これは可能ですか?どんなガイダンスも大歓迎です。

4

4 に答える 4

75

続行する方法の1つを次に示します(他の方法もあります。ビューモデルにそのまま一致する方法を示しているだけです):

[AttributeUsage(AttributeTargets.Property)]
public class RequireAtLeastOneOfGroupAttribute: ValidationAttribute, IClientValidatable
{
    public RequireAtLeastOneOfGroupAttribute(string groupName)
    {
        ErrorMessage = string.Format("You must select at least one value from group \"{0}\"", groupName);
        GroupName = groupName;
    }

    public string GroupName { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        foreach (var property in GetGroupProperties(validationContext.ObjectType))
        {
            var propertyValue = (bool)property.GetValue(validationContext.ObjectInstance, null);
            if (propertyValue)
            {
                // at least one property is true in this group => the model is valid
                return null;
            }
        }
        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
    }

    private IEnumerable<PropertyInfo> GetGroupProperties(Type type)
    {
        return
            from property in type.GetProperties()
            where property.PropertyType == typeof(bool)
            let attributes = property.GetCustomAttributes(typeof(RequireAtLeastOneOfGroupAttribute), false).OfType<RequireAtLeastOneOfGroupAttribute>()
            where attributes.Count() > 0
            from attribute in attributes
            where attribute.GroupName == GroupName
            select property;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var groupProperties = GetGroupProperties(metadata.ContainerType).Select(p => p.Name);
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage
        };
        rule.ValidationType = string.Format("group", GroupName.ToLower());
        rule.ValidationParameters["propertynames"] = string.Join(",", groupProperties);
        yield return rule;
    }
}

それでは、コントローラーを定義しましょう。

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new SomeViewModel();
        return View(model);        
    }

    [HttpPost]
    public ActionResult Index(SomeViewModel model)
    {
        return View(model);
    }
}

とビュー:

@model SomeViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>

@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.IsA)
    @Html.ValidationMessageFor(x => x.IsA)
    <br/>
    @Html.EditorFor(x => x.IsB)<br/>
    @Html.EditorFor(x => x.IsC)<br/>

    @Html.EditorFor(x => x.IsY)
    @Html.ValidationMessageFor(x => x.IsY)
    <br/>
    @Html.EditorFor(x => x.IsZ)<br/>
    <input type="submit" value="OK" />
}

残っている最後の部分は、クライアント側の検証用にアダプターを登録することです。

jQuery.validator.unobtrusive.adapters.add(
    'group', 
    [ 'propertynames' ],
    function (options) {
        options.rules['group'] = options.params;
        options.messages['group'] = options.message;
    }
);

jQuery.validator.addMethod('group', function (value, element, params) {
    var properties = params.propertynames.split(',');
    var isValid = false;
    for (var i = 0; i < properties.length; i++) {
        var property = properties[i];
        if ($('#' + property).is(':checked')) {
            isValid = true;
            break;
        }
    }
    return isValid;
}, '');

特定の要件に基づいて、コードが適応される場合があります。

于 2011-08-30T18:46:58.950 に答える
1

ブール値ではなく文字列に追加したことを除いて、Darinの素晴らしい回答をアプリケーションに実装しました。これは、名前/会社、または電話/電子メールのようなものでした。1つの小さな問題を除いて、私はそれを愛していました。

職場の電話、携帯電話、自宅の電話、または電子メールなしでフォームを送信しようとしました。クライアント側で 4 つの個別の検証エラーが発生しました。エラーを解消するために入力できるフィールドをユーザーが正確に知ることができるため、これは私にとっては問題ありません。

メールアドレスを入力しました。現在、電子メールでの単一の検証はなくなりましたが、電話番号での 3 つの検証は残っていました。これらもエラーではなくなりました。

そこで、これを考慮して検証をチェックする jQuery メソッドを再割り当てしました。以下のコード。それが誰かを助けることを願っています。

jQuery.validator.prototype.check = function (element) {

   var elements = [];
   elements.push(element);
   var names;

   while (elements.length > 0) {
      element = elements.pop();
      element = this.validationTargetFor(this.clean(element));

      var rules = $(element).rules();

      if ((rules.group) && (rules.group.propertynames) && (!names)) {
         names = rules.group.propertynames.split(",");
         names.splice($.inArray(element.name, names), 1);

         var name;
         while (name = names.pop()) {
            elements.push($("#" + name));
         }
      }

      var dependencyMismatch = false;
      var val = this.elementValue(element);
      var result;

      for (var method in rules) {
         var rule = { method: method, parameters: rules[method] };
         try {

            result = $.validator.methods[method].call(this, val, element, rule.parameters);

            // if a method indicates that the field is optional and therefore valid,
            // don't mark it as valid when there are no other rules
            if (result === "dependency-mismatch") {
               dependencyMismatch = true;
               continue;
            }
            dependencyMismatch = false;

            if (result === "pending") {
               this.toHide = this.toHide.not(this.errorsFor(element));
               return;
            }

            if (!result) {
               this.formatAndAdd(element, rule);
               return false;
            }
         } catch (e) {
            if (this.settings.debug && window.console) {
               console.log("Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e);
            }
            throw e;
         }
      }
      if (dependencyMismatch) {
         return;
      }
      if (this.objectLength(rules)) {
         this.successList.push(element);
      }
   }

   return true;
};
于 2014-07-11T16:23:48.397 に答える
0

これが古いスレッドであることは知っていますが、同じシナリオに出くわし、いくつかの解決策を見つけて、上記のマットの質問を解決するものを見たので、この回答に出くわした人のために共有したいと思いました. チェックアウト: MVC3 入力の控えめな検証グループ

于 2013-05-22T16:41:38.043 に答える