View with Multiple Models and Posting only one - c#

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.

Related

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

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!

Get password from a user with UserManager

I'm making a website with MVC5 ASP.NET.
I'm using Identity framework 2.0 implement class with properties such as passwordhash, username, email, emailconfirmed and so on. I'm using userManager.ChangePassword(user.Id, Oldpassword, Newpassword);, but i can't figure out how i should get a password from a user as plain text (string)
[HttpPost]
public ActionResult ChangePassword(AspNetUsersViewModel userView)
{
UserManager<IdentityUser> userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>());
var result = userManager.ChangePassword(_User.Id, "123456789", userView.Password);
return RedirectToAction("Index", "ConfigUser");
}
As now I'm i have hardcoded users current password "123456789" to test if it works, and it does.
I hope you guys can help.
Add password input to the View inside the form tag
<input type="password" id= "userNewPassword" name="userNewPassword">
Pass the userNewPasswor as string to the controller after the userView and the pass it to the UserManager
[HttpPost]
public ActionResult ChangePassword(
AspNetUsersViewModel userView,
string userNewPassword){
UserManager<IdentityUser> userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>());
var result = userManager.ChangePassword(_User.Id, userNewPassword , userView.Password);
return RedirectToAction("Index", "ConfigUser");
}
Note: the Best way is to Modify the userView and add the userNewPassword to the model
Update:
in the visual studio 2013 the if you used the asp.net default template you will find the flowing class
public class ChangePasswordBindingModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Current password")]
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 = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}

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.

#Html.DropDownListFor fails in partial view but not in Full Page View

I am having an issue where my PartialView DropDownListFor gets the error:
The ViewData item that has the key PlanId is of type System.int32 but must be of type
IEnumerable<SelectListItem>
#Html.DropDownListFor(model => model.PlanId, (SelectList)ViewBar.PlanNameSelectList, new {#class = "short" })
This error does not pop up when I go to the page that originally held this code. What I have done is gutted the core part of the code which has worked previously with another partialView, as long as I took out the DropDownListFor elements in the code. I did not need them for that partialView, but now that I need them the problem has come full circle.
I would greatly appreciate any help that can be given to me to help solve this problem. Other resources like calls to the partial are below
#Html.Partial("location", new MAO.Models.ViewModels.CreateTemplateModel{})
This is the model
public class CreateTemplateModel {
[Required(ErrorMessage = "{0} is required.")]
[RegularExpression("^[0-9]+$", ErrorMessage="Template Id can only contain numbers")]
[Display(Name = "Template ID")]
public string TNumber { get; set; }
[Required(ErrorMessage = "{0} is required.")]
[RegularExpression("^.[0-9]{4}(-[0-9]{3})?$", ErrorMessage = "H# Must follow either #XXXX or #XXXX-XXX pattern")]
[Display(Name = "HNumber")]
public string HNumber { get; set; }
[RequiredIfOtherIsEmpty("NewPlanName", ErrorMessage = "Please enter a Plan Name")]
[Display(Name = "Select Existing Plan Name")]
public int PlanId { get; set; }
[MaxLength(500, ErrorMessage="{0} can't be longer than 500 characters")]
[Display(Name = "Enter New Plan Name")]
public string NewPlanName { get; set; }
[RequiredIfOtherIsEmpty("NewParentOrganization", ErrorMessage = "Please enter a Parent Organization")]
[Display(Name = "Select Existing Parent Organization")]
public string ParentOrganization { get; set; }
[MaxLength(500, ErrorMessage = "{0} can't be longer than 500 characters")]
[Display(Name = "Enter New Parent Organization")]
public string NewParentOrganization { get; set; }
[Required(ErrorMessage = "{0} is required.")]
public int TemplateTypeId { get; set; }
}
There is a controller that is pretty long so I am not going to post that. If there are parts of the controller that would be helpful I can post those parts as well as anything else that I might have forgotten to include
Based on your comments, I'm suspecting that you're never rebinding your drop down list when you are returning your partial view. Your controller action for the partial should be building your dropdown list in an identical manner to the controller action that renders the full view. Compare the two and make sure that they match.
UPDATE: Your partial view action should look something like the following:
public ActionResult Location()
{
ViewBag.PlanNameSelectList = new SelectList(plans.Distinct(), "Id", "Name", plans.FirstOrDefault(plan => plan.Name == selectedPlan));
attachSelectLists(ViewBag);
return PartialView("Location");
}
What you are currently doing with
#Html.Partial("location", new MAO.Models.ViewModels.CreateTemplateModel{})
Is rendering the partial view "location" using a NEW CreateTemplateModel object, not an existing one. Instead, a better way to do it is to duplicate your controller actions. Create a new one specifically for your partial view (this is a simpler use case for now).
public ActonResult TestPartialView()
Instead of using #Html.Partial which renders a partial, try calling your new controller action instead, which will build your drop down list for you.
#Html.RenderAction("TestPartialView").
This will call your new controller action and render the partial on the page, preserving the controller logic. If you use #Html.Partial, it simply renders the partial view passing in whatever object you give it which, in this case, is a new, empty CreateTemplateModel.

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