How to validate two properties with ASP.NET MVC 2 - asp.net-mvc

I'm just getting started with ASP.NET MVC 2, and playing around with Validation.
Let's say I have 2 properties:
Password1
Password2
And I want to require that they are both filled in, and require that both are the same before the model is valid.
I have a simple class called "NewUser".
How would I implement that? I've read about ValidationAttribute, and understand that. But I don't see how I would use that to implement a validation that compares two or more properties against eathother.
Thanks in advance!
Problem with below solution:
When this is applied to an application, and the ModelBinder runs the validation of the Model, then there is a problem:
If a Property-level ValidationAttribute contains an error, then the Class-level ValidationAttribute's are NOT validated. I have not found a solution to this problem as of yet.
If you have a solution to this problem please share your experience. Thanks alot!

The default ASP.NET MVC 2 template of Visual Studio includes the exact validation attribute you need. Pasted from AccountModels.cs file :
[AttributeUsage(AttributeTargets.Class,
AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute {
private const string _defaultErrorMessage =
"'{0}' and '{1}' do not match.";
private readonly object _typeId = new object();
public PropertiesMustMatchAttribute(string originalProperty,
string confirmProperty)
: base(_defaultErrorMessage) {
OriginalProperty = originalProperty;
ConfirmProperty = confirmProperty;
}
public string ConfirmProperty { get; private set; }
public string OriginalProperty { get; private set; }
public override object TypeId {
get {
return _typeId;
}
}
public override string FormatErrorMessage(string name) {
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
OriginalProperty, ConfirmProperty);
}
public override bool IsValid(object value) {
PropertyDescriptorCollection properties =
TypeDescriptor.GetProperties(value);
object originalValue = properties.Find(OriginalProperty,
true /* ignoreCase */).GetValue(value);
object confirmValue = properties.Find(ConfirmProperty,
true /* ignoreCase */).GetValue(value);
return Object.Equals(originalValue, confirmValue);
}
}
How to use :
[PropertiesMustMatch("Password", "ConfirmPassword",
ErrorMessage = "The password and confirmation password do not match.")]
class NewUser {
[Required]
[DataType(DataType.Password)]
[DisplayName("Password")]
public string Password { get; set; }
[Required]
[DataType(DataType.Password)]
[DisplayName("Confirm password")]
public string ConfirmPassword { get; set; }
}

Related

How to Transfer DataAnnotation metadata to ViewModel with AutoMapper with Betty's Approach

I need clarification on how to implement Betty's code solution to transferring data annotation metadata to ViewModels with AutoMapper (see here). Or if you have a better way, please share that. Maybe the implementation of Betty's answer is obvious to someone who knows AutoMapper well, but I'm new to it.
Here is a simple example, what do I add to this code to make Betty's solution work:
// Data model Entity
public class User1
{
[Required]
public int Id { get; set; }
[Required]
[StringLength(60)]
public string FirstName { get; set; }
[Required]
[StringLength(60)]
public string LastName { get; set; }
[Required]
[DataType(DataType.Password)]
[StringLength(40)]
public string Password { get; set; }
}
// ViewModel
public class UserViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
}
Current AutoMapper Implementation:
// Called once somewhere
Mapper.CreateMap<User1, UserViewModel>(MemberList.Destination);
// Called in controller method, or wherever
User user = new User() { FirstName = "Tony", LastName = "Baloney", Password = "secret", Id = 10 };
UserViewModel userVM = Mapper.Map<User, UserViewModel>(user);
// NOW WHAT???
I've tried this in global.asax in Application_Start:
var configProvider = Mapper.Configuration as IConfigurationProvider;
ModelMetadataProviders.Current = new MetadataProvider(configProvider);
ModelValidatorProviders.Providers.Clear(); // everything's broke when this is not done
ModelValidatorProviders.Providers.Add(new ValidatorProvider(configProvider));
Also, I had to modify Betty's GetMappedAttributes from:
propertyMap.DestinationProperty.GetCustomAttributes to:
propertyMap.DestinationProperty.MemberInfo.GetCustomAttributes
(or instead of MemberInfo, is it MemberType?) for this to even build.
But nothing seems to work.
The metadata provider isn't used by Automapper, it uses Automapper.
You don't need to call it directly, it's automatically called by MVC as long as you register it with MVC on startup in Global.asax.cs, for example:
ModelMetadataProviders.Current = new MetadataProvider(
AutoMapper.Mapper.Engine.ConfigurationProvider);
ModelValidatorProviders.Providers.Add(new ValidatorProvider(
AutoMapper.Mapper.Engine.ConfigurationProvider);
or:
ModelMetadataProviders.Current = new MetadataProvider(
(AutoMapper.IConfigurationProvider)AutoMapper.Mapper.Configuration);
ModelValidatorProviders.Providers.Add(new ValidatorProvider(
(AutoMapper.IConfigurationProvider)AutoMapper.Mapper.Configuration));

MVC 4 Inserting Model Data into a database using Repository Framework

Here is my ListView Model which moreorless corresponds with a datbase table I have built called Comment.
public int EntryId { get; set; }
public DateTime Posted { get; set; }
public string AuthorIp { get; set; }
[Required(ErrorMessage = " A Name is required *")]
[DisplayFormat(ConvertEmptyStringToNull = false)]
[StringLength(160, MinimumLength = 2, ErrorMessage = "Must be between 2 & 160 characters in length.")]
public string AuthorName { get; set; }
[Required(ErrorMessage = "Email address required *")]
[DisplayFormat(ConvertEmptyStringToNull = false)]
[StringLength(160, MinimumLength = 2, ErrorMessage = "Must be between 2 & 160 characters in length *")]
[EmailValidation(ErrorMessage = "Must be valid email *")]
public string AuthorEmail { get; set; }
[Required(ErrorMessage = " A Message is required *")]
[DisplayFormat(ConvertEmptyStringToNull = false)]
[StringLength(4000, MinimumLength = 2, ErrorMessage = "Must be between 2 & 4000 characters in length *")]
public string Body { get; set; }
public ListView(IBlogRepository blogRepository)
{
Posts = blogRepository.Posts();
}
public ListView(){ }`
I need to get some of the properties into my Comment table. Iam using or at least attempting to use the IRepository Framework. It goes like this...
public interface IBlogRepository : IDisposable
{
IList<Entry> Posts();
void InsertComment(Comment comment);
void Save();
}
Here is my inherit class...
public class BlogRepository : IBlogRepository, IDisposable
{
private BlogDataDataContext _dataContext;
public BlogRepository() { _dataContext = new BlogDataDataContext(); }
// #region IBlogRepository Members
public void InsertComment(Comment comment)
{
_dataContext.Comments.InsertOnSubmit(comment);
}
public void Save()
{
_dataContext.SubmitChanges();
}
So I call above InsertComment from my BlogController like this.
[HttpPost]
public ActionResult BlogPost(ListView pModel)
{
pModel.Posted = DateTime.Now;
_repository.InsertComment( // Cannot pass pModel as is not Comment type.
return RedirectToAction("BlogPost");
}
So my problem is that my ListView pModel is passed in but its not of type Comment so I cant Insert it properly. I need my ListView model because it contains extra validation rules and a couple of constructors. Anyone have an idea of where I am going wrong.
Do I need to create a Model which directly mirrors the datbase table I am asdding too. Then where do I move my Constructors and other validation rules? It feels like I need two models. One on top of the other. I'm so close to understanding this now.
Thanks in advance.
If you use ASP.NET MVC your model should be an exact "copy" of your table.
If you want some other informations to pass to your view you can use viewmodels.
In your example why don't you do something like:
[HttpPost]
public ActionResult BlogPost(ListView pModel)
{
pModel.Posted = DateTime.Now;
foreach (Comment comment in ListView.Posts)
{
_repository.InsertComment(comment);
}
return RedirectToAction("BlogPost");
}
I think your approach looks good overall, but you'll need some way of obtaining that current Comment object to pass into your repository. Whether you create another object to house both objects, or refactor your code, I would recommend looking at the following article:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
You could use AutoMapper to map properties from your ViewModel to your Comments business model in your controller:
var comment = Mapper.Map<ListView, Comment>(pModel);
_repository.InsertComment(comment);
This is common practice when working with ViewModels or DTO's.

Customizing DisplayFormat beyond DataFormatString

I have a MVC 4 project where I would like to use functionality similar to DisplayFromat, but setting a DataFormatString is not enough. I would like a function to be called to format the string. Is that possible?
I have tested inheriting DisplayFormat but that just lets me set the DataFormatString.
I have looked at customizing DataAnnotationsModelMetadataProvider, but I don't see how I would make it call a custom function for formatting.
My particular case is that I need to format the integer 201351 as "w51 2013". I couldn't come up with a format string that does that.
The easiest way is to expose a read-only property on your Model:
public class Model{
public int mydata{get; set;}
public string formattedDate{
get{
string formattedval;
// format here
return formattedval;
};
}
}
You can create a custom ValidationAttribute. Here is some code I use to validation someone has selected a drop down value.
using System.ComponentModel.DataAnnotations;
public sealed class PleaseSelectAttribute : ValidationAttribute
{
private readonly string _placeholderValue;
public override bool IsValid(object value)
{
var stringValue = value.ToString();
if (stringValue == _placeholderValue || stringValue == "-1")
{
ErrorMessage = string.Format("The {0} field is required.", _placeholderValue);
return false;
}
return true;
}
public PleaseSelectAttribute(string placeholderValue)
{
_placeholderValue = placeholderValue;
}
}
Then Use it:
[Required]
[Display(Name = "Customer")]
[PleaseSelect("Customer")]
public int CustomerId { get; set; }

MVC disable unobtrusive validation for specific validator

I'm currently building a MVC4 project which uses unobtrusive validation through data annotations.
I have a specific field, "PostalCode", which is both [Required] and has a [RegularExpression] validator attached to it.
The thing is, this regular expression should only be verified if a specific country is selected. (This country will be the default value and we can assume in nearly all cases this will be used)
Now I need some way to disable this regex validation when a different country is selected, while keeping the required validator active.
Nearly all sollutions I've found are using a jQuery.validator.defaults.ignore filter, but this would disable both validators on that item.
Any thoughts on how to best tackle this problem?
Edit: Small code snippet to show how this is working.
[Required]
[RegularExpression("^[1-9]\\d{3} ?[a-zA-Z]{2}$"] //Should only be verified if Country == "The Netherlands"
string PostalCode{get;set;}
[Required]
string Country {get;set;}
In the end I made up my own ValidationAttribute based on this blog post: http://thewayofcode.wordpress.com/2012/01/18/custom-unobtrusive-jquery-validation-with-data-annotations-in-mvc-3/
It's an elegant sollution and required way less work than I anticipated.
Edit: As per request, I provide the sollution created by myself:
// DependentRegularExpressionAttribute.cs
/// <summary>
/// Only performs a regular expression validation if a specified other property meets a validation requirement
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class DependentRegularExpressionAttribute : ValidationAttribute, IClientValidatable
{
private readonly Regex _regex;
private readonly string _otherPropertyName;
private readonly Regex _otherPropertyRegex;
public DependentRegularExpressionAttribute(string regex, string otherPropertyName, string otherPropertyRegex)
{
_regex = new Regex(regex);
_otherPropertyName = otherPropertyName;
_otherPropertyRegex = new Regex(otherPropertyRegex);
}
/// <summary>
/// Format the error message filling in the name of the property to validate and the reference one.
/// </summary>
/// <param name="name">The name of the property to validate</param>
/// <returns>The formatted error message</returns>
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, _regex, _otherPropertyName, _otherPropertyRegex);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var validationResult = ValidationResult.Success;
if (value == null || String.IsNullOrEmpty(value as string))
return validationResult;
// Using reflection we can get a reference to the other property
var otherPropertyInfo = validationContext.ObjectType.GetProperty(_otherPropertyName);
var otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue == null || String.IsNullOrEmpty(otherPropertyValue as string))
return validationResult;
if (_otherPropertyRegex.IsMatch(otherPropertyValue.ToString()))
{
if (!_regex.IsMatch(value.ToString()))
validationResult = new ValidationResult(ErrorMessage);
}
return validationResult;
}
#region IClientValidatable Members
/// <summary>
///
/// </summary>
/// <param name="metadata"></param>
/// <param name="context"></param>
/// <returns></returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
string errorMessage = FormatErrorMessage(metadata.DisplayName ?? metadata.PropertyName);
// The value we set here are needed by the jQuery adapter
var dependentRegexRule = new ModelClientValidationRule
{
ErrorMessage = errorMessage,
ValidationType = "dependentregex"
};
//"otherpropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE!
dependentRegexRule.ValidationParameters.Add("otherpropertyname", _otherPropertyName);
dependentRegexRule.ValidationParameters.Add("regex", _regex);
dependentRegexRule.ValidationParameters.Add("otherpropertyregex", _otherPropertyRegex);
yield return dependentRegexRule;
}
#endregion
}
// customvalidation.js
$.validator.addMethod("dependentregex", function (value, element, params) {
var regex = new RegExp(params[0]);
var otherElement = document.getElementById(params[1]);
var otherElementRegex = new RegExp(params[2]);
if (!value || !otherElement.value)
return true;
if (otherElementRegex.test(otherElement.value)) {
if (!regex.test(element.value))
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add("dependentregex", ["regex", "otherpropertyname", "otherpropertyregex"], function(options) {
options.rules["dependentregex"] = [options.params.regex,
options.params.otherpropertyname,
options.params.otherpropertyregex];
options.messages["dependentregex"] = options.message;
});
Inside my viewmodel I do the following:
[Display(Name = "Customer_PostalCode", ResourceType = typeof(Resources.DisplayNames))]
[DependentRegularExpression("^[1-9]\\d{3} ?[a-zA-Z]{2}$", "CorrespondenceCountry", "Nederland", ErrorMessageResourceType = typeof(Resources.Validation), ErrorMessageResourceName = "Customer_PostalCode")] //"Nederland" is a regular expression in this case
[Required(ErrorMessageResourceType = typeof(Resources.Validation), ErrorMessageResourceName = "Shared_RequiredField")]
public string CorrespondenceZipCode { get; set; }
In the end, the customvalidation.js method basically does the exact same thing as the C# code. A detailed explanation of what everything does can be found in the blogpost I referenced
It seems like you want a "required if" validation attribute. I would check out http://foolproof.codeplex.com/ - it has an implementation that you can use like so (lifted directly from the project's site):
private class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public bool Married { get; set; }
[RequiredIfTrue("Married")]
public string MaidenName { get; set; }
}
Take a look at this, I haven't used it myself, but it seems to suit your needs http://foolproof.codeplex.com/workitem/18974
They have an example that looks like this:
[RequiredIf("Country", Operator.RegExMatch, "(1033|4105)", ErrorMessage = "{0} is required")]
public string State { get; set; }
[Required(ErrorMessage = "{0} is required")]
public int Country { get; set; }

asp.net MVC DataAnnotations

Please ask if you can't understand what I'm asking.
I have created a custom ValidateAttribute for my ViewModel
i created it for validate properties which depend from another property of ViewModel
if (user checked "01" or "09" from QrupList) Then
Company name is needed
Name,surname and LastName are not needed
else
Company name is not needed
Name,surname and LastName are needed
I have ViewModel as below
[ValidateForGroupAttribute("Group", "CompanyName")]
public partial class AbonentViewModel
{
[DisplayName("Şirkət")]
public string CompanyName { get; set; }
[DisplayName("Soyadı")]
[Required(ErrorMessage = "Soyadı vacibdir")]
public string Surname { get; set; }
[DisplayName("Qrup")]
[Required(ErrorMessage = "Qrup vacibdir")]
public string Group{ get; set; }
public SelectList GroupList { get; set; }
}
My custom ValidationAttribute classes:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class ValidateForGroupAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' a müvafiq '{1}' daxil din";
public ValidateForGroupAttribute(string originalProperty, string confirmPropertyCompany)
: base(_defaultErrorMessage)
{
OriginalProperty = originalProperty;
ConfirmPropertyCompany = confirmPropertyCompany;
}
public string OriginalProperty { get; private set; }
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
OriginalProperty, ConfirmPropertyCompany);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
object originalValue = properties.Find(OriginalProperty, true).GetValue(value);
object confirmValueCompany = properties.Find(ConfirmPropertyCompany, true).GetValue(value);
if ((string)originalValue == "01" || (string)originalValue == "09")
return false;
else
return true;
}
}
How do I do it? What is wrong in my ValidationAttributes?
We looked at validation using data annotations a few months back, and decided it was better to use something like fluent validation, as we had complex business rules and logic that would have taken too much effort to realise with data annotations. Have a look at the documentation, and you will see fluent validation makes things like this easy.
Sorry, I did not get back sooner: Check fluent validation here
Your rule could look something like. Syntax not tested, but I am sure you will be able to figure it out.
public class AbonentViewModelValidator : AbstractValidator<AbonentViewModel> {
public AbonentViewModelValidator() {
RuleFor(model => model.CompanyName).NotEmpty().When(model => (model.GroupList.Id == 1 || model.GroupList.Id == 9 ));
RuleFor(model => model.Surname).NotEmpty().When(model => (model.GroupList.Id != 1 || model.GroupList.Id != 9 ));
RuleFor(model => model.Name).NotEmpty().When(model => (model.GroupList.Id != 1 || model.GroupList.Id != 9 ));
}
}

Resources