8

私は WebForms で何年も働いてきましたが、.NET の MVC のフレーバーにはかなり慣れていません。実行時に動的検証ルールをモデルのメンバーに適用する方法を見つけようとしています。この質問の目的のために、これらは私が扱っているクラスの簡略化されたバージョンです:

public class Device
{
   public int Id {get; set;}
   public ICollection<Setting> Settings {get; set;}
}

public class Setting
{
   public int Id {get; set;} 
   public string Value {get; set;}
   public bool IsRequired {get; set;}
   public int MinLength {get; set;}
   public int MaxLength {get; set;}
}

私の見解では、それぞれのエディターを使用して Settings コレクションを反復処理し、実行時に各 Setting インスタンスに含まれる検証ルールを適用して、コンパイル時にモデルで DataAnnotations を使用することで得られるのと同じクライアント側とサーバー側の検証を実現します。 . WebForms では、関連するフィールドに適切な Validator を添付しただけですが、MVC4 で同様のメカニズムを見つけるのに苦労しています。これを達成する方法はありますか?

4

4 に答える 4

9

私の解決策は、ValidationAttribute クラスを拡張し、IClientValidatable インターフェイスを実装することでした。以下は、改善の余地のある完全な例です。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Web.Mvc;

namespace WebApplication.Common
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
    public class RuntimeRequiredAttribute : ValidationAttribute, IClientValidatable
    {
        public string BooleanSwitch { get; private set; }
        public bool AllowEmptyStrings { get; private set; }

        public RuntimeRequiredAttribute(string booleanSwitch = "IsRequired", bool allowEmpytStrings = false ) : base("The {0} field is required.")
        {
            BooleanSwitch = booleanSwitch;
            AllowEmptyStrings = allowEmpytStrings;
        }

            protected override ValidationResult IsValid(object value, ValidationContext validationContext)
            {
                PropertyInfo property = validationContext.ObjectType.GetProperty(BooleanSwitch);

                if (property == null || property.PropertyType != typeof(bool))
                {
                    throw new ArgumentException(
                        BooleanSwitch + " is not a valid boolean property for " + validationContext.ObjectType.Name,
                        BooleanSwitch);
                }

                if ((bool) property.GetValue(validationContext.ObjectInstance, null) &&
                    (value == null || (!AllowEmptyStrings && value is string && String.IsNullOrWhiteSpace(value as string))))
                {
                    return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
                }

                return ValidationResult.Success;
            }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
            ControllerContext context)
        {
            object model = context.Controller.ViewData.Model;
            bool required = (bool)model.GetType().GetProperty(BooleanSwitch).GetValue(model, null);

            if (required)
            {
                yield return
                    new ModelClientValidationRequiredRule(
                        FormatErrorMessage(metadata.DisplayName ?? metadata.PropertyName));
            }
            else
            //we have to return a ModelCLientValidationRule where
            //ValidationType is not empty or else we get an exception
            //since we don't add validation rules clientside for 'notrequired'
            //no validation occurs and this works, though it's a bit of a hack
            {
                yield return
                    new ModelClientValidationRule {ValidationType = "notrequired", ErrorMessage = ""};
            }
        }
    }
}

上記のコードは、検証用のスイッチとして使用するモデルのプロパティを探します (IsRequired がデフォルトです)。スイッチとして使用されるブール プロパティが true に設定されている場合、クライアント側とサーバー側の両方の検証が、 で装飾されたプロパティに対して実行されますRuntimeRequiredValdiationAttribute。このクラスは、バリデーション スイッチに使用されているモデルのどのプロパティも、編集のためにエンド ユーザーに表示されないことを想定していることに注意してください。つまり、これは RequiredIf バリデータではありません。

here で概説されているように、クライアント側の検証とともに ValidationAttribute を実装する別の方法が実際にあります。比較のために、私が上で行った IClientValidatable ルートは、同じ著者によってここで概説されています

これは現在、ネストされたオブジェクトでは機能しないことに注意してください。たとえば、属性が別のオブジェクトに含まれるオブジェクトのプロパティを装飾する場合、機能しません。この欠点を解決するためのオプションがいくつかありますが、これまでのところ、私には必要ありませんでした。

于 2013-09-23T17:41:26.100 に答える
3

RemoteAttributeを使用できます。これにより、サーバーへの邪魔にならない ajax 呼び出しが実行され、データが検証されます。

于 2013-09-20T15:16:27.840 に答える
1

上記のコメントで述べたように、リフレクションを使用して同様のことを行いました。その一部は無視できます。たとえば、辞書はおそらく必要ありません。これは、カスタムの翻訳可能なメッセージを提供する方法にすぎないためです。

サーバー側コード:

 private static Dictionary<string, ILocalisationToken> _requiredValidationDictionary;

 private static Dictionary<string, ILocalisationToken> RequiredValidationDictionary(UserBase model)
 {
      if (_requiredValidationDictionary != null)
          return _requiredValidationDictionary;

      _requiredValidationDictionary = new Dictionary<string, ILocalisationToken>
      {
             { model.GetPropertyName(m => m.Publication), ErrorMessageToken.PublicationRequired},
             { model.GetPropertyName(m => m.Company), ErrorMessageToken.CompanyRequired},
             { model.GetPropertyName(m => m.JobTitle), ErrorMessageToken.JobTitleRequired},
             { model.GetPropertyName(m => m.KnownAs), ErrorMessageToken.KnownAsRequired},
             { model.GetPropertyName(m => m.TelephoneNumber), ErrorMessageToken.TelephoneNoRequired},
             { model.GetPropertyName(m => m.Address), ErrorMessageToken.AddressRequired},
             { model.GetPropertyName(m => m.PostCode), ErrorMessageToken.PostCodeRequired},
             { model.GetPropertyName(m => m.Country), ErrorMessageToken.CountryRequired}
      };
      return _requiredValidationDictionary;

  }

  internal static void SetCustomRequiredFields(List<string> requiredFields, UserBase model, ITranslationEngine translationEngine)
  {
      if (requiredFields == null || requiredFields.Count <= 0) return;
      var tokenDictionary = RequiredValidationDictionary(model);
      //Loop through requiredFields and add Display text dependant on which field it is.
  foreach (var requiredField in requiredFields.Select(x => x.Trim()))
  {
      ILocalisationToken token;

      if (!tokenDictionary.TryGetValue(requiredField, out token))
         token = LocalisationToken.GetFromString(string.Format("{0} required", requiredField));

      //add to the model.
      model.RequiredFields.Add(new RequiredField
      {
         FieldName = requiredField,
         ValidationMessage = translationEngine.ByToken(token)
      });
      }
  }

  internal static void CheckForRequiredField<T>(ModelStateDictionary modelState, T fieldValue, string fieldName,                                                            IList<string> requiredFields,                                                          Dictionary<string, ILocalisationToken> tokenDictionary)
   {
        ILocalisationToken token;
        if (!tokenDictionary.TryGetValue(fieldName, out token))
           token = LocalisationToken.GetFromString(string.Format("{0} required", fieldName));
        if (requiredFields.Contains(fieldName) && (Equals(fieldValue, default(T)) || string.IsNullOrEmpty(fieldValue.ToString())))
             modelState.AddModelError(fieldName, token.Translate());
   }

  internal static void CheckForModelErrorForCustomRequiredFields(UserBase model,                                                                             Paladin3DataAccessLayer client, ICache cache,                                                                             ModelStateDictionary modelState)
  {

      var requiredFields = Common.CommaSeparatedStringToList                          (client.GetSettingValue(Constants.SettingNames.RequiredRegistrationFields, cache: cache, defaultValue: String.Empty, region: null)).Select(x => x.Trim()).ToList();
      var tokenDictionary = RequiredValidationDictionary(model);

      foreach (var property in typeof(UserBase)             .GetProperties(BindingFlags.Instance |                                               BindingFlags.NonPublic |                                               BindingFlags.Public))
      {
            CheckForRequiredField(modelState, property.GetValue(model, null), property.Name, requiredFields, tokenDictionary);
      }
  }

モデルには、List<RequiredField>基本的に 2 つの文字列を持つクラスである があります。1 つはフィールド名用で、もう 1 つはエラー メッセージ用です。

モデルをビューに渡したら、サーバー側のチェックを行う場合は、ページに検証を追加するために少し jQuery が必要です。

クライアント側コード:

   $("#YOURFORM").validate();
        for (var x = 0; x < requiredFields.length; x++) {
            var $field = $('#' + requiredFields[x].FieldName.trim());

            if ($field.length > 0) {
                $field.rules("add", {
                      required: true,
                      messages: {
                           required: "" + requiredFields[x].ValidationMessage  
                           //required: "Required Input"
                      }
                });

            $field.parent().addClass("formRequired"); //Add a class so that the user knows its a required field before they submit

                 }

          }

これのいずれかが非常に明確でない場合はお詫び申し上げます。ご不明な点がございましたら、お気軽にお問い合わせください。説明できるよう最善を尽くします。

于 2013-09-20T22:42:37.217 に答える