The code which is generally generated for a ASP.NET MVC 3 Membership, escpecially the property NewPassword of the class ChangePasswordModel looks roughly like:
[Required]
[StringLength(100, MinimumLength=6)]
[DataType(DataType.Password)]
[Display("Name = CurrentPassword")]
public string NewPassword { get; set; }
In order to to fill some information with external parameters I am using RecourceType:
(In this case I am changing OldPassword and fill the attribute Display with some additional Data from a Resource
[Required]
[DataType(DataType.Password)]
[Display(ResourceType = typeof(Account), Name = "ChangePasswordCurrent")]
public string OldPassword { get; set; }
Back to NewPassword. How can I substitute the MinimumLenght with Membership.MinRequiredPasswordLength? : My attempt:
[Required]
[StringLength(100, MinimumLength=Membership.MinRequiredPasswordLength)]
[DataType(DataType.Password)]
[Display(ResourceType=typeof(Account), Name= "ChangePasswordNew")]
public string NewPassword { get; set; }
This produces the error:
An attribute argument must be a constant expression, typeof expression
or array creation expression of an attribute parameter type
(http://msdn.microsoft.com/de-de/library/09ze6t76%28v=vs.90%29.aspx)
Validation attributes must be compiled constants (like your error message states). You could create you own ValidationAttribute that handles this minimum length for you.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ValidatePasswordLengthAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "'{0}' must be at least {1} characters long.";
private readonly int _minCharacters = Membership.Provider.MinRequiredPasswordLength;
public ValidatePasswordLengthAttribute() : base(DefaultErrorMessage)
{
}
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentUICulture, ErrorMessageString, name, _minCharacters);
}
public override bool IsValid(object value)
{
var valueAsString = value.ToString();
return (valueAsString != null) && (valueAsString.Length >= _minCharacters);
}
}
Then your view model could look like this (you could even get more fancy and add the max length part of your DataAnnotations in the ValidatePasswordLength attribute to remove that line)
[Required]
[StringLength(100)]
[DataType(DataType.Password)]
[Display(ResourceType=typeof(Account), Name= "ChangePasswordNew")]
[ValidatePasswordLength]
public string NewPassword { get; set; }
Related
I have this asp.net core model:
public class MyModel
{
[ModelBinder(Name = "id")]
[StringLength(36, MinimumLength = 3)]
public string ObjectId { get; set; }
}
I added the ModelBinder attribute in order to rename the "ObjectId" field to "id".
When I trying to submit the model with bad values. For example:
{
"id": "1111111111111111111111111111111111111111111111111111111111111111111111111111"
}
I'm getting back this response from the server:
{
"id":["The field ObjectId must be a string with a minimum length of 3 and a maximum length of 36."]
}
Expected output:
{
"id":["The field id must be a string with a minimum length of 3 and a maximum length of 36."]
}
That's strange because the key ("id") was written in the right case. But in the value ("ObjectId") it was written in wrong.
My client shouldn't be aware of ObjectId. He just know id. How to fix messages like these?
Thanks.
The solution is to use DisplayName attribute:
public class MyModel
{
[ModelBinder(Name = "id")]
[StringLength(36, MinimumLength = 3)]
[DisplayName("id")]
public string Id {get; set;}
}
You can set a custom error message on StringLengthAttribute Class.
public class MyModel
{
[ModelBinder(Name = "id")]
[StringLength(36, MinimumLength = 3, ErrorMessage="The field id must be a string with a minimum length of {1} and a maximum length of {2}.")]
public string ObjectId { get; set; }
}
Excerpt from documentation page:
You can use composite formatting placeholders in the error message: {0} is the name of the property; {1} is the maximum length; and {2} is the minimum length. The placeholders correspond to arguments that are passed to the String.Format method at runtime.
For StringLength, it uses the default property name for constructing the error message which is expected.
If you prefer using the Name arugment from ModelBinder, you could implement your own StringLength arribute like
public class CustomStringLength : StringLengthAttribute
{
public CustomStringLength(int maximumLength)
: base(maximumLength)
{
}
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyName = validationContext.DisplayName;
var propertyAttribute = validationContext.ObjectType
.GetProperty(propertyName)
.GetCustomAttribute(typeof(ModelBinderAttribute));
if (propertyAttribute is ModelBinderAttribute modelBinderAttribute)
{
validationContext.DisplayName = modelBinderAttribute.Name;
}
//validationContext.DisplayName = "Id";
return base.IsValid(value, validationContext);
}
}
Then use it like
public class MyModel
{
[ModelBinder(Name = "id")]
[CustomStringLength(36, MinimumLength = 3)]
public string ObjectId { get; set; }
}
I have a class Student that contains the list of property 'TextPair' as shown below:
public class Student
{
public List<TextPair> Hobbies { get; set; }
public List<TextPair> Languages { get; set; }
public List<TextPair> Majors { get; set; }
}
public class TextPair
{
[StringLength(2, ErrorMessage = "The value length is invalid")]
public string Value { get; set; }
public string Text { get; set; }
}
Here, I validate the value for maximum length 2 using the StringLength AttributeValidator and decorate in the property 'Value' inside TextPair model.
The problem for me is that the length is always fixed and length is always mandatory.
In my use case, I want the different flavor of Value in different part of the application (or, different property of same type) to support different lengths.
I was looking for something like below where I could pass the validation in my class where I declare my property 'TextPair'
[i.e. I don't want to make the validation mandatory always and also not hard-code the value 2]
public class Student
{
//Any length of the value is accepted for hobbies
public List<TextPair> Hobbies{ get; set; }
[ValuesLength(Length = 2, ErrorMessage = "Language code length must be 2 characters max")]
public List<TextPair> Languages { get; set; }
[ValuesLength(Length = 128, ErrorMessage = "The major should be within 128 characters length")]
public List<TextPair> Majors{ get; set; }
}
Is there any efficient way to approach this solution?
Maybe try subclassing StringLengthAttribute to create your desired ValuesLength attribute. Please note that this code is not tested and is just suggestion to final implementation.
public class ValuesLength : StringLengthAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var isValid = true;
var pair = value as TextPair;
if (pair != null && pair.Value != null)
{
var pairValue = pair.Value;
isValid = pairValue.Length < MaximumLength && pairValue.Length > MinimumLength;
}
return IsValid ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
}
One of the solution approached is as follows:
public class Student
{
//Any length of the value is accepted for hobbies
public List<TextPair> Hobbies{ get; set; }
[ValuesLength(MaximumLength = 2, ErrorMessage = "Language code length must be 2 characters max")]
public List<TextPair> Languages { get; set; }
[ValuesLength(MaximumLength = 128, ErrorMessage = "The major should be within 128 characters length")]
public List<TextPair> Majors{ get; set; }
}
My Custom Attribute validation is checking the list and verifying if anyone of the element values are exceeding the provided length as:
public class ValuesLengthAttribute : ValidationAttribute
{
public int MaximumLength { get; set; }
public override Boolean IsValid(object value)
{
Boolean isValid = true;
var list = value as List<TextPair>;
if (list != null && list.Count>0)
foreach (var item in list)
{
if (item.Value.Length > MaximumLength)
isValid = false;
}
return isValid;
}
}
In my model I have an object that has the following property.
[Range(typeof(int), "2014", "2024", ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }
The lower and upper range values are 2014 and 2024 respectively. However, rather than use these fixed values, I'd like them to be based on another property in the model.
So, for example, if I had a property, CurrentFiscalYear, my hypothetical Range attribute would look like this.
[Range(typeof(int), CurrentFiscalYear, CurrentFiscalYear + 10, ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }
Is something like this possible? Or must the lower and upper values be provided at compile time?
No, this isn't possible. Attribute parameter values just be "compile-time constant" values. In other words, the actual value of the parameter must be known when you compile the program.
From MSDN - Attributes tutorial:
Attribute parameters are restricted to constant values of the following types:
Simple types (bool, byte, char, short, int, long, float, and double)
string
System.Type
enums
object (The argument to an attribute parameter of type object must be a constant value of one of the above types.)
One-dimensional arrays of any of the above types
This is documentation for .NET 1.1, but has not changed.
Workaround
This isn't tested at all but you can create a custom ValidationAttribute which takes the range and also model property names who's values to add to the range values when testing for validity. You can create an internal standard RangeAttribute to do the work for you and even keep client validation working by implementing IClientValidatable:
public sealed class ShiftedRangeAttribute : ValidationAttribute
{
public string MinShiftProperty { get; private set; }
public string MaxShiftProperty { get; private set; }
public double Minimum { get; private set; }
public double Maximum { get; private set; }
public ShiftedRangeAttribute(double minimum, double maximum, string minShiftProperty, string maxShiftProperty)
{
this.Minimum = minimum;
this.Maximum = maximum;
this.MinShiftProperty = minShiftProperty;
this.MaxShiftProperty = maxShiftProperty;
}
public ShiftedRangeAttribute(int minimum, int maximum, string minShiftProperty, string maxShiftProperty)
{
this.Minimum = minimum;
this.Maximum = maximum;
this.MinShiftProperty = minShiftProperty;
this.MaxShiftProperty = maxShiftProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
RangeAttribute attr = this.CreateRangeAttribute(validationContext.ObjectInstance);
return attr.GetValidationResult(value, validationContext);
}
internal RangeAttribute CreateRangeAttribute(object model)
{
double min = this.Minimum;
if (this.MinShiftProperty != null)
{
min += Convert.ToDouble(model.GetType().GetProperty(this.MinShiftProperty).GetValue(model));
}
double max = this.Maximum;
if (this.MaxShiftProperty != null)
{
max += Convert.ToDouble(model.GetType().GetProperty(this.MaxShiftProperty).GetValue(model));
}
return new RangeAttribute(min, max);
}
}
If you want it to work with client validation, you will also to create a DataAnnotationsModelValidator and register it in your global.asax Application_Start() to ensure the client validation HTML attributes are output. Again you can cheat and use the built-in RangeAttributeAdapter to help you because in Javascript it is ultimately just a range validator:
public class ShiftedRangeAttributeAdapter : DataAnnotationsModelValidator<ShiftedRangeAttribute>
{
public ShiftedRangeAttributeAdapter(ModelMetadata metadata, ControllerContext context, ShiftedRangeAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
RangeAttribute attr = this.Attribute.CreateRangeAttribute(this.Metadata.Container);
return new RangeAttributeAdapter(this.Metadata, this.ControllerContext, attr).GetClientValidationRules();
}
}
...
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(ShiftedRangeAttribute), typeof(ShiftedRangeAttributeAdapter));
Note that the client validation code only works if the class containing the properties is the top-level model class, which is stored in Metadata.Container. You cannot access the "parent" of the current property. You would need to do more work to create a custom jQuery validator to handle this properly.
You can then use it as so:
[ShiftedRange(0, 10, "CurrentFiscalYear", "CurrentFiscalYear", ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }
EDIT: fixed some bugs after testing
This can be done by writing a custom ValidationAttribute, implementation could be done something like this:
public sealed class FiscalYearAttribute : ValidationAttribute
{
public string CurrentFiscalYear { get; set; }
public override bool IsValid(object value)
{
var currentFiscalYearString = HttpContext.Current.Request[CurrentFiscalYear];
var currentFiscalYear = int.Parse(currentFiscalYearString);
var fiscalYear = (int) value;
return fiscalYear >= currentFiscalYear && fiscalYear <= currentFiscalYear + 10;
}
public override string FormatErrorMessage(string name)
{
return name + " error description here.";
}
}
Usage:
[Required]
[Display(Name = "CurrentFiscalYear")]
public int CurrentFiscalYear { get; set; }
[Display(Name = "FiscalYear")]
[FiscalYear(CurrentFiscalYear = "CurrentFiscalYear")]
public int FiscalYear { get; set; }
I am trying to set-up a remote validation similar to the one in this example:
Example
My application has a twist however, my form elements are dynamically generated, therefore this tag:
[Remote("doesUserNameExist", "Account", HttpMethod = "POST", ErrorMessage = "User name already exists. Please enter a different user name.")]
is not set in stone, I need to vary the ErrorMessage for example and preferably vary the action. Is it possible, or would you suggest taking the long-way, meaning to implement the whole ajax validation on my own.
Any suggestions are appreciated.
If you need to have a dynamic error message then you could return this as string from your validation action:
public ActionResult DoesUserNameExist(string username)
{
if (Exists(uasername))
{
string errorMessage = "Some dynamic error message";
return Json(errorMessage, JsonRequestBehavior.AllowGet);
}
return Json(true, JsonRequestBehavior.AllowGet);
}
And if you need even more flexibility such as invoking dynamic dynamic actions, then you're better of rolling your custom validation solution instead of relying on the built-in Remote attribute.
You can inherit from RemoteAttribute and make it fetch the required values from a service or factory according to your own logic. Here is an example:
[AttributeUsage(AttributeTargets.Property)]
public class MyRemoteAttribute : RemoteAttribute
{
public MyRemoteAttribute(Type type, string propertyName)
: base(MyRemoteAttributeDataProvider.GetAttributeData(type,propertyName).Action, MyRemoteAttributeDataProvider.GetAttributeData(type,propertyName).Controller)
{
var data = MyRemoteAttributeDataProvider.GetAttributeData(type,propertyName);
base.ErrorMessage = data.ErrorMessage;
base.HttpMethod = data.HttpMethod;
}
}
public static class MyRemoteAttributeDataProvider
{
public static RemoteAttributeData GetAttributeData(Type type
, string propertyName)
{
//this is where you are going to implement your logic im just implementing as an example
//you can pass in a different type to get your values. For example you can pass in a service to get required values.
//property specific logic here, again im going to implement to make this
//specification by example
var attrData = new RemoteAttributeData();
if(propertyName == "MyOtherProperty")
{
attrData.Action = "MyOtherPropertyRelatedAction";
attrData.Controller = "MyOtherPropertyRelatedController";
attrData.ErrorMessage = "MyOtherPropertyRelated Error Message";
attrData.HttpMethod = "POST";
}
else
{
attrData.Action = "UserNameExists";
attrData.Controller = "AccountController";
attrData.ErrorMessage = "Some Error Message";
attrData.HttpMethod = "POST";
}
return attrData;
}
}
public class RemoteAttributeData
{
public string Controller { get; set; }
public string Action { get; set; }
public string HttpMethod { get; set; }
public string ErrorMessage { get; set; }
}
And this is how you are supposed to use is:
public class RegisterViewModel
{
[Required]
[Display(Name = "User name")]
[MyRemote(typeof(RegisterViewModel),"UserName")]
public string UserName { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[System.ComponentModel.DataAnnotations.Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Required]
[MyRemote(typeof(RegisterViewModel),"MyOtherProperty")]
public string MyOtherProperty { get; set; }
}
As I also mentioned above at the commentary. You should specialize that provider according to your needs.
I hope this helps.
UPDATE:
I update the implementation based on your comment, so that it takes a property name and does some property name specific wiring.
I have an MVC Model class that looks like this:
[ValidateNewRegistration]
public class NewRegistrationModel {
[System.ComponentModel.DisplayName("Profile Display Name")]
[Required(ErrorMessage = "Display Name must be specified.")]
[StringLength(50)]
public string DisplayName { get; set; }
[System.ComponentModel.DisplayName("Email Address")]
[Required(ErrorMessage = "Email address must be specified.")]
[RegularExpression("^[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}$",
ErrorMessage = "The email address you specified is invalid.")]
[StringLength(100)]
public string EmailAddress1 { get; set; }
}
In my view, I have this:
#Html.ValidationSummary()
#Html.EditorForModel()
I understand how to use the metadata to mark up the attributes, but I want to do is implement my own Editor Template that has two fields in it ... a first name and last name, that will be used in at least 1/2 dozen places in my website.
How do I add error messages so that they appear in the Validation Summary, but in a way that is contained within the EditorTemplate?
Long term what I am looking for is the ability to put a "[UIHint("--Editor Template--")]" into the classes, and have the editor template self-contained enough to issuing it's onw error message for the form field. So that I do not have to add metadata to the class that says [Required...] or [RegularExpression...]. My end goal is a class that looks like this:
[ValidateNewRegistration]
public class NewRegistrationModel {
[System.ComponentModel.DisplayName("Profile Display Name")]
[UIHint("DisplayName")]
public string DisplayName { get; set; }
[System.ComponentModel.DisplayName("Email Address")]
[UIHint("EmailAddress")]
public string EmailAddress1 { get; set; }
[System.ComponentModel.DisplayName("Email Address (confirm)")]
[UIHint("EmailAddress")]
public string EmailAddress2 { get; set; }
}
A similar question has already been asked here: Is there a way to reuse data annotations?
Check the solutions there and comment if you need help with anything specific
class UIHintAttribute : ValidationAttribute
{
public string Field { get; set; }
public UIHintAttribute(string field)
{
Field = field;
}
public override bool IsValid(object value)
{
if (Field == "DisplayName")
{
if (new RequiredAttribute { ErrorMessage = "DisplayName cannot be blank." }.IsValid(value) && new StringLengthAttribute(50).IsValid(value))
return true;
return false;
}
if (Field == "EmailAddress")
{
if (new RequiredAttribute { ErrorMessage = "Email address cannot be blank." }.IsValid(value)
&& new RegularExpressionAttribute("^[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}$") { ErrorMessage = "The email address you specified is invalid." }.IsValid(value)
&& new StringLengthAttribute(100).IsValid(value))
return true;
return false;
}
// Needs to return true by default unless Required exists
return true;
}
}
Now you should be able to use it the way you wanted to.
Edit:
Long term what I am looking for is the ability to put a
"[UIHint("--Editor Template--")]" into the classes, and have the
editor template self-contained enough to issuing it's onw error
message for the form field. So that I do not have to add metadata to
the class that says [Required...] or [RegularExpression...].
The class above will make the following possible:
public class NewRegistrationModel {
[System.ComponentModel.DisplayName("Profile Display Name")]
[UIHint("DisplayName")]
public string DisplayName { get; set; }
[System.ComponentModel.DisplayName("Email Address")]
[UIHint("EmailAddress")]
public string EmailAddress1 { get; set; }
[System.ComponentModel.DisplayName("Email Address (confirm)")]
[UIHint("EmailAddress")]
public string EmailAddress2 { get; set; }
}