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; }
}
Related
Currently, this is what my model class looks like, with the custom validation attribute
Client.cs
[Required]
[DisplayName("Bookkeeping")]
public bool Bookkeeping { get; set; }
[Required]
[DisplayName("Personal Income Taxation")]
public bool Personal_Income_Taxation { get; set; }
[Required]
[DisplayName("Self-Employed Business Taxes")]
public bool Self_Employed_Business_Taxes { get; set; }
[Required]
[DisplayName("GST/PST/WCB Returns")]
public bool GST_PST_WCB_Returns { get; set; }
[Required]
[DisplayName("Tax Returns")]
public bool Tax_Returns { get; set; }
[Required]
[DisplayName("Payroll Services")]
public bool Payroll_Services { get; set; }
[Required]
[DisplayName("Previous Year Filings")]
public bool Previous_Year_Filings { get; set; }
[Required]
[DisplayName("Govt. Requisite Form Applicaitons")]
public bool Government_Requisite_Form_Applications { get; set; }
[StringLength(220, ErrorMessage = "Cannot exceed more than 220 characters")]
public string Other { get; set; }
[CheckboxAndOtherValidation(nameof(Bookkeeping),
nameof(Personal_Income_Taxation),
nameof(Self_Employed_Business_Taxes),
nameof(GST_PST_WCB_Returns),
nameof(Tax_Returns),
nameof(Payroll_Services),
nameof(Previous_Year_Filings),
nameof(Government_Requisite_Form_Applications), ErrorMessage = "At least one of the checkboxes or the 'Other' field must be filled")]
public bool AreCheckboxesAndOtherValid { get; set; }
CheckboxAndOtherValidation.cs
public class CheckboxAndOtherValidation : ValidationAttribute
{
readonly object TRUE = true;
string[] _alltheOtherProperty;
public CheckboxAndOtherValidation(params string[] alltheOthersProperty)
{
_alltheOtherProperty = alltheOthersProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var errorMessage = FormatErrorMessage((validationContext.DisplayName));
bool IsOtherNull = false;
bool IsAnyCheckboxChecked = false;
if (_alltheOtherProperty?.Count() > 0 != true)
{
return new ValidationResult(errorMessage);
}
var otherPropertyInfo = validationContext.ObjectType.GetProperty(nameof(Client.Other));
if (otherPropertyInfo != null)
{
object otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue == null || string.IsNullOrEmpty(otherPropertyValue.ToString()))
{
IsOtherNull = true;
}
}
for (var i = 0; i < _alltheOtherProperty.Length; ++i)
{
var prop = _alltheOtherProperty[i];
var propertyInfo = validationContext.ObjectType.GetProperty(prop);
if (propertyInfo == null)
{
continue;
}
object propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
if (Equals(TRUE, propertyValue))
{
IsAnyCheckboxChecked = true;
}
}
if (IsOtherNull && !IsAnyCheckboxChecked)
return new ValidationResult(errorMessage);
else
return ValidationResult.Success;
}
}
I want to be able to have client side validation when submitting a form. All other fields of my form work from the client side except the custom validation that I have, by doing something like this
<span class="text-danger col-3" asp-validation-for="Client.Name"></span>
How can I get the custom validation attribute to work the same way? (Note: client side scripting works when I include a file in my razor page called "ValidationScriptsPartial", in which there are references to multiple jquery files. I believe thats how client side validation is happening)
How can I get the custom validation attribute to work the same way?
If you'd like to do same validation logic on client side as you did on custom server-side validation attribute on model property.
You can try to implement custom client-side validation based on your actual requirement, for more information, please refer to this doc:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-3.1#custom-client-side-validation
Besides, if possible, you can try to use the [Remote] attribute that also enables us to validate combinations of fields, which might help you achieve same requirement easily.
I have an action method which outputs a model which has multiple sub models. In one of the sub model I have some additional properties which are not required in my view.
Sub model- ProjectModel-
[Required(ErrorMessage = "*")]
public int Id { get; set; }
[Required(ErrorMessage = "*")]
public int SectorDivisionId { get; set; }
[Required(ErrorMessage = "*")]
[StringLength(250, ErrorMessage = "Project name should not be more than 250 characters.")]
public string Program { get; set; }
[Required(ErrorMessage = "*")]
[StringLength(25, ErrorMessage = "Project number should not be more than 25 characters.")]
public string ProjectNumber { get; set; }
public string WorkPackage { get; set; }
public string WorkPackageType { get; set; }
[Required(ErrorMessage = "*")]
public DateTime StartDate { get; set; }
[Required(ErrorMessage = "*")]
public DateTime EndDate { get; set; }
public int ProjectDirectorId { get; set; }
So while initializing the sub model to my main model I am only using those properties which I need as shown below.
model.ProjectInfo = new ProjectModel()
{
Id = projectId,
ProjectNumber = prj.p.ProjectNumber,
Director = prj.Director,
Program = prj.p.Program,
StartDate = prj.p.StartDate,
EndDate = prj.p.EndDate,
ProjectReviewPeriodList = projectReviewPeriodList.AsEnumerable().
Select(o => new ProjectReviewPeriodModel
{
Id = o.Id,
ProjectReviewTypeId = o.ProjectReviewTypeId,
ProjectId = o.ProjectId,
ReviewPeriod = o.ReviewPeriod,
ReviewPeriodDate = o.ReviewPeriodDate
}).ToList()
};
Now, while posting the form I have an action filter at global level which validates the Model. The validation (ModelState.IsValid) fails for some of the fields from the sub model which I haven't initialized as per my needs.
I thought of two options-
Using ModelState.Remove(<PropertyName>) to skip validation. This is not possible as I am using a global level action filter.
Create a new view model
Is there any other way of doing this, preferably in the action method level?
Please let me know if any doubts or I can explain it more clearly.
Thanks.
The clean way would be to use different ViewModels for different usecases.
That being said, you can implement the validation logic with IValidatableObject instead of using Data Annotations attributes.
Introduce a flag into the ViewModel that indicates the usecase, e.g. IsEditUsecase. Set this flag somewhere where you know the usecase, e.g. in the controller.
Then only perform the validations that are needed for this usecase.
public class ProjectModel : IValidatableObject {
public bool IsEditUsecase { get; set; }
[Required(ErrorMessage = "*")] // required for every usecase
public int Id { get; set; }
// no [Required] and [StringLength] attributes
// because only required in some of the usecases
public string Program { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// validation that occurs only in Edit usecase
if (IsEditUsecase) {
// Validate "Program" property
if (string.IsNullOrWhiteSpace(Program)) {
yield return new ValidationResult(
"Program is required",
new[] { "Program" }
);
}
else if (Program.Length > 250) {
yield return new ValidationResult(
"Project name should not be more than 250 characters.",
new[] { "Program" }
);
}
// validate other properties for Edit usecase ...
}
// validate other usecases ...
}
}
As a dirty hack, I have added hidden fields in my razor page for all those properties which caused ModelState validation error. Basically I added some default values for the hidden fields and it works fine now.
Not recommended though but it was a quick fix.
I know this has been asked many times but I can't solve my problem which is very funny.
My model is simple:
public class RegisterModel
{
[Display(Name = "First name")]
[Required]
public string FirstName { get; set; }
[Display(Name = "Last name")]
[Required]
public string LastName { get; set; }
[Display(Name = "Email address")]
[RegularExpression(#"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage = "Enter valid e-mail")]
[Required(ErrorMessage = "E-mail is empty")]
public string EmailAddress { get; set; }
public System.Web.Mvc.SelectList Countries { get; set; }
}
Action:
[AllowAnonymous]
public virtual ActionResult Register(RegisterModel data)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
ModelState.Clear();
var countries =
this.ClientRepositoryBuilder
.CountryClientRepository
.GetAllCountries();
data.Countries = new SelectList(countries, "Id", "CountryName");
return
this.View(data);
}
else
{
return
this.RedirectToAction(MVC.Home.Index());
}
}
As soon as I add Countries into my model, stuff stops working and it doesn't invoke POST Action, give me an error with firebug(it's ajax post):
No parameterless constructor defined for this object
Default Model binding can't deal with that object when trying to convert from information coming in request to RegisterModel.
Options:
separate "incoming request model" from "view model" (as it looks like you always setting that property in controller).
create custom model binding - check more MSDN: The Features and Foibles of ASP.NET MVC Model Binding, or SO When to use IModelBinder versus DefaultModelBinder.
If you want something like accepting list as request parameter - Model Binding to a List MVC 4 may have an answer.
Ok I did a mistake with type, don't even know how I did this mistake.. So I updated model with proper type for Countries and stuff works now:
[Required]
public int Countries { get; set; }
It was a mistake due mine tiredness ;)
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.
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; }