Validation error with readonly input attribute and [Required] data Annotation - c#

As we know when you create an ASP.NET Core appp using Individual User Authentication project template, it creates a default ResetPassword.cshtml view. In that View I need to set logged in user name input tag as readonly. But doing so is throwing the following validation error. If I don't make it readonly the below screen successfully allows user to change password.
Question: Why the following validation error on form submit - when the UserName input tag is set to readonly? I know that if the input tag is disabled then form submit does not submit the input tag's value (also explained by #AdamBellaire here). It seems [Required] annotation in public string UserName { get; set; } is somehow conflicting with readonly attribute of input tag.
public class ResetPasswordViewModel
{
[Required]
public string UserName { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public string Code { get; set; }
}
UPDATE
ResetPassword.cshtml: [It's a default View created by VS2017. Only change is that I added readonly attribute in input tag below]
#model ResetPasswordViewModel
...
<input asp-for="#User.Identity.Name" class="form-control" readonly />
...

In this situation, it really makes no sense to expect #User.Identity.Name in the view to bind to UserName in the view model.
It would seem that the code the IDE generated is wrong. Maybe a messed up scaffolding template somewhere, who knows.
You need to change your asp-for to equal UserName. See below.
<input asp-for="UserName" class="form-control" readonly />
Glad this helped!

Related

Validation for EmailAdress gets triggered for optional field

I have this email validation attribute
[EmailAddress]
[StringLength(50, ErrorMessage = "Email is too long!")]
public string Email { get; set; }
And it works fine, but the problem is when I type the email, and then erase it, the form still asks me to insert correct email even though the field is not required.
it will accept empty string or exact an email id
"^$|^([\w\.\-]+)#([\w\-]+)((\.(\w){2,3})+)$"
Answer

View with Multiple Models and Posting only one

The situation is that I have a complex model with a lots of data to view and aside to it, control panel with for example password change.
One big model with another property model which will be submitted.
The information inside the big model requires loading and is not required upon POSTing
The Model
public class ProfileModel {
// This is the submitted model:
public PasswordChangeModel Password = new PasswordChangeModel();
// Personal Info
public string Name {get; set;}
public string LastName {get; set;}
// 15~ more fields
}
The password model w/ validation
public class PasswordChangeModel {
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "OldPassword")]
public string OldPassword { 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 = "Repeat password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string RepeatPassword { get; set; }
}
Controller Catching-Action
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult ChangePassword(PasswordChangeModel model) {
if (!ModelState.IsValid) //validate the model
return View(model);
//do stuff ...
return Index();
}
The Html to generate the form
<form asp-controller="Profile" asp-action="ChangePassword" asp-antiforgery="true">
<div asp-validation-summary="ValidationSummary.ModelOnly" class="text-danger"></div>
<label asp-for="Password.OldPassword">Old Password</label>
<input asp-for="Password.OldPassword"/>
<label asp-for="Password.Password">New Password</label>
<input asp-for="Password.Password"/>
<label asp-for="Password.RepeatPassword">New Password Repeat</label>
<input asp-for="Password.RepeatPassword"/>
<input type="submit" class="btn" name="submit" value="Change"/>
</form>
The Question
Now after reviewing the code, my question is - is it possible to submit it that way, if not whats the most convenient and clean way to do it.
Note: I always can just include 3 fields inside the model ProfileModel of the password changing but A-It's ugly and B-It still makes the entire ProfileModel data to load.
I would say that the cleanest way to do this is to have a separate update password view. This or switching to a ajax post so you can post without reloading the page. If you can't make a model that could do a roundtrip to the server without repopulating it then don't do standard form posting. It can be done but when I've seen it usually there are subtle errors when rerendering the page on validation error.
It's just easy to shoot yourself in the foot.
This is what I've ended up doing.
Worked just fine.
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult ChangePassword([Bind(Prefix = "Password.OldPassword")]string Password_OldPassword,
[Bind(Prefix = "Password.Password")] string Password_Password,
[Bind(Prefix = "Password.RepeatPassword")] string Password_RepeatPassword) {
//Change the password
}
Bind attribute redirected the value of Password.OldPassword to Password_OldPassword.

Setting up password/confirm password without [Compare]

I am using Asp.net MVC to create a password and confirm password field. I am currently using the remote attribute to check if password and confirm password are the same, however remote will only call a function if the box it is applied to is changed.
I've looked through previous posts going back to last year and found the most common suggestion to be the compare attribute, however it is now deprecated.
I assume there is a pre-built solution for this problem.
This is from the model
[Remote(UserController.ActionNameConstants.PasswordMatch,
UserController.NameConst, AdditionalFields = "ConfirmPassword",
ErrorMessage = "The passwords do not match")]
[MinLength(8, ErrorMessage="Minimum password length is 8")]
[DisplayName("Password"), RequiredIf("Id == 0",
ErrorMessage="Password is required")]
public string Password { get; set; }
[DisplayName("Confirm Password"), RequiredIf("Id == 0",
ErrorMessage = "Confirm password is required")]
public string ConfirmPassword { get; set; }
This is in the controller
[HttpGet]
public virtual JsonResult PasswordMatch(string password,string confirmPassword)
{
return this.Json(password ==
confirmPassword,JsonRequestBehavior.AllowGet);
}
Compare is not depricate you can still use [Compare("Property name to compare with")]... it's in "System.ComponentModel.DataAnnotations" namespace.

Populate mvc dropdownlist based on user role

I'm adding a dropdown list to the view on my MVC project. The dropdown list will be populated based on the user type; a user can create a new user with the same or a lower access level. The access levels are based on the user 'role' assigned when the user is authenticated.
So for example, an administrator has the highest access level. He / she can create any user type, i.e.
Administrator
Manager
Supervisor
CSR
ReadOnly
A manager can create a manager, supervisor etc. (And so on.)
I have a 'Register' view where the user will enter the details to create a new user - username / password and access level. A dropdown list will give them the list of user types they can create.
My question is this: How should I populate the dropdown list?
I have the logic in my controller and I create the dropdown list and pass it to the view. But the problem is, as expected - when 'Register' is clicked, the dropdown list will be lost.
I can create the dropdown list in the view, but is this 'best' practice?
I have my code below for reference; any suggestions are greatly appreciated.
In the ViewModel:
public class RegisterViewModel
{
[Required]
[Display(Name = "User name")]
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")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Required]
[Display(Name = "User type")]
public IEnumerable<System.Web.Mvc.SelectListItem> UserType { get; set; }
}
View:
#Html.LabelFor(m => m.UserType, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.UserType, Model.UserType, new { #class = "form-control" })
In the controller:
public ActionResult Register()
{
CSR.Models.ViewModels.Account.RegisterViewModel rvm = new CSR.Models.ViewModels.Account.RegisterViewModel();
rvm.UserType = GetRoles();
return View(rvm);
}
private IEnumerable<SelectListItem> GetRoles()
{
var roles = RolesList().Select(x => new SelectListItem { Value = x.accessLevelID.ToString(), Text = x.accessLevel });
return new SelectList(roles, "Value", "Text");
}
The RolesList() method returns a list of users based on User.IsInRole.
I am thinking the logic could be in the view so that the dropdown list is populated each time, but in my inexperienced opinion this seems to go against the 'MVC' pattern.
Advice is very much appreciated, thank you.
You can use this:
Create ActionFilter PopulateUserRoles (this filter adds to ViewData[Constants.UserTypes] collection of items where Constants.UserTypes is string constant "UserTypes"); example of such filter and you can inject inside filter everything you need
Decorate your Action by this attribute
Result:[PopulateUserRoles]
public ActionResult Register()...
...
#Html.DropDownListFor(x => x.UserType, ViewData[Constants.UserTypes]...
This option isn't required additional GetRoles() method in controller and additional IEnumerable UserType property in viewmodel. This option could be improved by using UIHint.

ASP.NET MVC 4 Cross field or property validation

I'm trying to figure out how to validate that a user has entered matching passwords when they sign up. Is there anything built in to MVC 4 Data Annotations that I can use for this or is the only route creating a custom validation attribute?
If I do have to create a custom validation attribute, how do I access the password property (assuming I put the annotation on the confirm password property)? Also, are there any commonly used libraries for this type of validation?
This is what I have for the beginning of a custom validation attribute, just not sure how to access the password property:
public class CrossFieldValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value) //how do I get the other value in here?
{
//validation logic here
return base.IsValid(value);
}
}
I appreciate any help!
There is already a comparison validation attribute built into mvc. See the documentation here:
http://msdn.microsoft.com/en-us/library/system.web.mvc.compareattribute(v=vs.98).aspx
An example of use would be:
public string Password { get; set; }
[Compare("Password", ErrorMessage = "Uh oh")]
public string PasswordAgain { get; set; }
You can create custom attributes and set additional information to their public properties.
public class CustomValidationAttribute : ValidationAttribute
{
public string MeaningfulValidationInfo { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// do whatever meaningful with MeaningfulValidationInfo
return base.IsValid(value, validationContext);
}
}
You'd set the additional info this way:
[CustomValidationAttribute(MeaningfulValidationInfo = "blah")]
public ActionResult Index()
{
return View();
}
If you are trying to check if both entered passwords were identical, you can simply validate that in your model.
public class LoginModel
{
[Required]
[EmailAddress]
public string EmailAddress { get; set; }
[Required]
public string Password { get; set; }
[Required]
[Compare("Password")]
[Display(Name = "Confirm password")]
public string ConfirmPassword { get; set; }
}
}
Compare annotation is the easiest option for this. As you can see below, Compare points to the Password attribute.
[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")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
You could use the *Compare Validate * control inbuilt on the ASP.NET Tools
I've provided a sample below
<body>
<form id="form1" runat="server">
<div>
<asp:Label
id="lblBirthDate"
Text="Birth Date:"
AssociatedControlID="txtBirthDate"
Runat="server" />
<asp:TextBox
id="txtBirthDate"
Runat="server" />
<asp:CompareValidator
id="cmpBirthDate"
Text="(Invalid Date)"
ControlToValidate="txtBirthDate"
Type="Date"
Operator="DataTypeCheck"
Runat="server" />
<br /><br />
<asp:Button
id="btnSubmit"
Text="Submit"
Runat="server" />
</div>
</form>
</body>
Please refer to any of the links below to widen your knowledge
http://www.java2s.com/Tutorial/ASP.NET/0160__Validation/CompareValidatorperformsthreedifferenttypesofvalidations.htm
http://www.vkinfotek.com/aspnetvalidationcontrols.html

Categories