Динамически применять правила проверки во время выполнения с помощью ASP.NET MVC 4

Я работаю в WebForms в течение многих лет, но я довольно новичок в .NET MVC. Я пытаюсь выяснить, как применять динамические правила проверки для членов моей модели во время выполнения. Для целей этого вопроса это упрощенные версии classов, с которыми я работаю:

public class Device { public int Id {get; set;} public ICollection 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 с редакторами для каждого и применять правила проверки, содержащиеся в каждом экземпляре установки во время выполнения, для достижения той же проверки на стороне клиента и на стороне сервера, что я получаю от использования DataAnnotations в моей модели во время компиляции , В WebForms я бы просто привязал соответствующий Validator к соответствующему полю, но у меня возникли проблемы с поиском аналогичного механизма в MVC4. Есть ли способ достичь этого?

Моим решением было расширить class 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 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 по умолчанию). Если для свойства boolean, используемого в качестве коммутатора, установлено значение true, тогда проверка на стороне клиента и на стороне сервера выполняется на свойстве, украшенном RuntimeRequiredValdiationAttribute . Важно отметить, что этот class предполагает, что любое свойство модели используется для переключателя проверки не будет отображаться конечному пользователю для редактирования, то есть это не валидатор RequiredIf.

Существует фактически другой способ реализации ValidationAttribute наряду с проверкой на стороне клиента, как описано здесь . Для сравнения, IClientValidatable маршрут, как я сделал выше, описан одним и тем же автором здесь .

Обратите внимание, что в настоящее время это не работает с вложенными объектами, например, если атрибут украшает свойство объекта, содержащегося в другом объекте, он не будет работать. Есть несколько вариантов решения этого недостатка, но пока это мне не понадобилось.

Вы можете использовать RemoteAttribute . Это должно выполнить ненавязчивый вызов ajax серверу для проверки ваших данных.

Как я сказал в своем комментарии выше, я сделал что-то подобное, используя reflection. Вы можете игнорировать некоторые из них, например, вам, вероятно, не нужен словарь, поскольку это всего лишь способ предоставить им настраиваемые переводимые сообщения.

Код на стороне сервера :

  private static Dictionary _requiredValidationDictionary; private static Dictionary RequiredValidationDictionary(UserBase model) { if (_requiredValidationDictionary != null) return _requiredValidationDictionary; _requiredValidationDictionary = new Dictionary { { 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 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(ModelStateDictionary modelState, T fieldValue, string fieldName, IList requiredFields, Dictionary 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 который в основном представляет собой class с двумя строками, один для имени поля и один для сообщения об ошибке.

После того как вы передали модель в представление, вам нужно немного 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 } } 

Извиняюсь, если что-то из этого не очень ясно. Не стесняйтесь задавать любые вопросы, и я сделаю все возможное, чтобы объяснить.

Я долго не работал с MVC4, поэтому прошу меня, если я ошибаюсь, но вы можете проверить на стороне сервера и на стороне клиента, используя jquery-val (уже ansible вам, если вы использовали шаблон «интернет-приложение» при создании своего проекта ) и атрибуты:

 public class Device { public int Id {get; set;} public ICollection Settings {get; set;} } public class Setting { [Required] public int Id {get; set;} [Range(1,10)] public string Value {get; set;} [Required] public bool IsRequired {get; set;} public int MinLength {get; set;} public int MaxLength {get; set;} }