Pull to refresh

Ненавязчивая валидация в ASP.NET MVC3

Reading time5 min
Views8.8K
Не так давно я начал разрабатывать сайты на ASP.NET MVC и в одном из моих проектов у меня появилась потребность в нестандартном валидаторе, который проверял бы обязательность заполнения элемента формы в зависимости от значения другого элемента. Именно об этом я и хочу рассказать.

Сайт разрабатывался на автомобильную тематику. Необходимость в валидаторе появилась на форме регистрации. Пользователь может зарегистрироваться как частное лицо и как автодиллер. Если пользователь хочет зарегистрироваться как автодиллер, то ему необходимо заполнить несколько дополнительных обязательных полей. Можно было конечно сделать регистрацию в несколько этапов, но хотелось чтобы весь процесс регистрации проходил за один шаг.


Серверная часть


Чтобы создать свой собственный валидатор, нам необходимо унаследоваться от класса ValidationAttribute.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
	//Сообщение, которое будет выводиться пользователю.
	private const string _defaultErrorMessage = "{0} is required";
   
	//Целевое поле
	private string _targetPropertyName;
	//Значение целевого поля
	private bool _targetPropertyCondition;

	public RequiredIfAttribute(string targetPropertyName, bool targetPropertyCondition)
		: base(_defaultErrorMessage)
	{
		this._targetPropertyName = targetPropertyName;
		this._targetPropertyCondition = targetPropertyCondition;
	}

	public override string FormatErrorMessage(string name)
	{
		return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, name, _targetPropertyName, _targetPropertyCondition);
	}

	protected override ValidationResult IsValid(object value, ValidationContext validationContext)
	{
		bool result = false;
		bool propertyRequired = false; // Флаг для проверки обязательности заполнения текущего поля.

		//Получим целевое поле
		var targetProperty = validationContext.ObjectType.GetProperty(_targetPropertyName);
		
		//Получим значние целевого поля
		var targetPropertyValue = (bool)targetProperty.GetValue(validationContext.ObjectInstance, null);

		//Если значение целевого поля соответствует заданному, то токущее поле должно быть заполнено.
		if (targetPropertyValue == _targetPropertyCondition)
		{
			propertyRequired = true;
		}

		if (propertyRequired)
		{
			//Если поле не заполнено
			if (value == null)
			{
				var message = FormatErrorMessage(validationContext.DisplayName);
				
				return new ValidationResult(message);  
			}
		}
	
		return null;
	}
 
	public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
	{
		var rule = new ModelClientValidationRule();

		rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
		rule.ValidationType = "requiredif";
		rule.ValidationParameters.Add("targetpropertyname", this._targetPropertyName);
		rule.ValidationParameters.Add("targetpropertyvalue", this._targetPropertyCondition.ToString().ToLower());

		yield return rule;
	}	
}


Определим для атрибута сообщение об ошибке, которое будет выдаваться пользователю в _defaultErrorMessage, а так же определим два поля _targetPropertyName и _targetPropertyCondition, в которых будет храниться имя поля в модели, в зависимости от которого будет проверяться обязательность заполнения текущего поля (для которого указан данный атрибут) и значение этого поля соответственно.

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



Клиентская часть


На этом серверная часть закончена. Перейдем к клиетской части. Для того что наш валидатор заработал на стороне клиента нам необходимо создать свою функцию валидации и свой адаптер.

$(function () {
    $.validator.addMethod("requiredif", function (value, element, param) {

       if ($(param.propertyname).is(':checked').toString() == param.propertyvalue) {
           if (!this.depend(param, element))
               return "dependency-mismatch";
           switch (element.nodeName.toLowerCase()) {
               case 'select':
                   var val = $(element).val();
                   return val && val.length > 0;
               case 'input':
                   if (this.checkable(element))
                       return this.getLength(value, element) > 0;
               default:
                   return $.trim(value).length > 0;
           }
       }

       return true;
    });


    $.validator.unobtrusive.adapters.add("requiredif", ["targetpropertyname", "targetpropertyvalue"], function (options) {
       if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
           options.rules["requiredif"] = {
               propertyname: "#" + options.params.targetpropertyname,
               propertyvalue: options.params.targetpropertyvalue
           };
           options.messages["requiredif"] = options.message;
       }
    });
} (jQuery));


Использование:


Для того чтобы использовать необходимо указать в модели для соответствующих полей атрибут RequiredIf:

public class RegisterModel
    {
       [Required]
       [Display(Name = "First name")]
       public string FirstName { get; set; }

       [Required]
       [Display(Name = "Last name")]
       public string LastName { get; set; }

       [Required]
       [DataType(DataType.EmailAddress)]
       [Display(Name = "Email address")]
       [RegularExpression("^([A-Za-z0-9_\\-\\.])+\\@([A-Za-z0-9_\\-\\.])+\\.([A-Za-z]{2,4})$", ErrorMessage = "Not a valid email.")]
       public string Email { get; set; }

       [Display(Name = "Dealer")]
       public bool IsDealer { get; set; }

       [RequiredIf("IsDealer", true)]
       [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 3)]
       [Display(Name = "Dealer name")]
       public string Name { get; set; }
    }


А так же добавить на страницу наш скрипт:
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.requiredif.js")" type="text/javascript"></script>


В результате мы получили достаточно удобный в использовании валидатор, который работает и на серверной и на клиентской стороне:



PS: По умолчанию в ASP.NET MVC3 режим ненавязчивой валидации отключен. Включить его можно двумя способами. Первый, используя файл Web.config:

  <appSettings>
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>


Так же можно указать в коде:

HtmlHelper.ClientValidationEnabled = true;
HtmlHelper.UnobtrusiveJavaScriptEnabled = true;


а так же необходимо добавить пару JavaScript файлов во View:

<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>
Tags:
Hubs:
Total votes 29: ↑22 and ↓7+15
Comments11

Articles