MVC Admin reset of User Password - asp.net-mvc

After a lot of messing around, and with some excellent help from ADyson, I got this working.
On my system, when an admin user logs in, a link appears for them to go into the user management system. This provides a list of users, the ability to create another user, delete them, or change their details.
Also, when ANY user logs in they are able to change their own password. However, if a user forgets their password, the admin user must reset the password. I'm not emailing them a link or anything fancy. In the "List Users" bit in the admin screen, there is an Actions column that contains links to edit, delete, show details, and reset password.
I have an ApplicationUsersController that contains the functions to edit, delete, etc. I have a series of ApplicationUsers views called Create, Edit, Delete, Details, Edit, Index. Most of this code was generated when I created an ApplicationUsersController and chose to create the views. There is also a ResetUserPasswordsViewModel as well. Here is the ResetPassword view:
#model ICWeb.Models.ResetUserPasswordViewModel
#{
ViewBag.Title = "Reset User Password";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>#ViewBag.Title</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "Please fix the errors displayed", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.NewPassword, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.NewPassword, new { htmlAttributes = new { #class = "form-control", #autofocus = "autofocus" } })
#Html.ValidationMessageFor(model => model.NewPassword, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ConfirmPassword, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ConfirmPassword, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ConfirmPassword, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Reset Password" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
In the controller I have:
// GET: /ApplicationUsers/ResetPassword
public ActionResult ResetPassword(string id)
{
return View(new ResetUserPasswordViewModel() { Id = id });
}
//POST: /ApplicationUsers/ResetPassword
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetUserPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
ApplicationDbContext context = new ApplicationDbContext();
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(context);
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(store);
string userId = model.Id;
string newPassword = model.NewPassword;
string hashedNewPassword = UserManager.PasswordHasher.HashPassword(newPassword);
ApplicationUser cUser = await store.FindByIdAsync(userId);
await store.SetPasswordHashAsync(cUser, hashedNewPassword);
await store.UpdateAsync(cUser);
return RedirectToAction("Index");
}
After a lot of messing around, I re-did this function. The view loads now and I can type in 2 new passwords. When I submit, the ResetPassword function runs. I can see when I step through the code it has the passwords I typed, and by editing the GET function to populate the model with the Id, I now get the Id of the user. The whole controller access is limited to users with admin permissions, so unless you're an admin you can't do anything here.
In my ResetUserPasswordModel I have:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace ICWeb.Models
{
public class ResetUserPasswordViewModel
{
public string Id { 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; }
}
}
All sorted, and the help was, and is, very much appreciated.

In the system I'm developing (but not yet completed or tested) I've got this written. It works so should be a good starting point. Note that the view model takes care of mis-match passwords so that is covered for you already.
I use Direct Injection for the User Manager - just replace my _userManager with your own instance, however you create it.
#model Models.ResetPasswordViewModel
#{
ViewBag.Title = "Reset password";
}
<div class="container">
#if (ViewBag.Error != null)
{
<div class="alert-danger mb-2">Error(s) occured : #ViewBag.Error</div>
#Html.ActionLink("Back to List", "AllUsers", null, new { #class = "btn btn-outline-primary" })
}
else
{
using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary("", new { #class = "text-danger" })
#Html.HiddenFor(x => x.Id)
<div class="form-group">
#Html.LabelFor(model => model.UserName, htmlAttributes: new { #class = "control-label" })
#Html.EditorFor(model => model.UserName, new { htmlAttributes = new { #class = "form-control", #readonly = "" } })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "control-label" })
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmPassword, new { #class = "control-label" })
#Html.PasswordFor(m => m.ConfirmPassword, new { #class = "form-control" })
</div>
<div class="form-group d-flex">
#Html.ActionLink("Back to User Edit", "EditUser", "Account", new { userId = Model.Id }, new { #class = "btn btn-outline-primary" })
<input type="submit" value="Reset Password" class="btn btn-primary ml-auto" />
</div>
}
}
</div>
public class ResetPasswordViewModel
{
[Display(Name = "User Id")]
public string Id { get; set; }
[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; }
}
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = _userManager.FindById(model.Id);
IdentityResult result = null;
if (user != null)
{
string code = await _userManager.GeneratePasswordResetTokenAsync(user.Id);
result = await _userManager.ResetPasswordAsync(user.Id, code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account", model);
}
}
// return errors
var s = new System.Text.StringBuilder();
//
foreach (var e in result.Errors)
{
if (s.Length > 0)
{
s.Append(", ");
}
s.Append(e);
}
ViewBag.Error = s.ToString();
return View();
}

Related

When updating database I get a EntityValidationError

I'm trying to get the logged in user to edit his profile. For now I decided to use a satic userID (since using the sessions usedID was also giving me errors). Now when I run the code it goes through all the lines but at the end it gives me this error "EntityValidationErrors".
I just want the code to update the current UserID's Bio, Sex, and preferred sex
[HttpPost]
public ActionResult FillProfile(tblUser user)
{
using (TrinityEntities db = new TrinityEntities())
{
var id = Session["userID"];
user.Id = 1018; //Convert.ToInt32(id);
SqlConnection conn = new SqlConnection(#"Data Source = (LocalDB)\MSSQLLocalDB; AttachDbFilename = C:\Users\Ruben\Desktop\Periode 2\Persoonlijk\Trinity\Trinity\Trinity\App_Data\Trinity.mdf; Integrated Security = True; MultipleActiveResultSets = True; Connect Timeout = 30; Application Name = EntityFramework");
conn.Open();
string Query = "SELECT FirstName, LastName, Email, Password, Age FROM tblUser WHERE Id='" + user.Id + "'";
using (SqlCommand command = new SqlCommand(Query, conn))
{
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
user.FirstName = reader[0] as string;
user.LastName = reader[1] as string;
user.Email = reader[2] as string;
user.Password = reader[3] as string;
string Sage = reader[4] as string;
user.Age = Convert.ToInt32(Sage);
break;
}
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
}
conn.Close();
return RedirectToAction("Index", "Home");
}
}
View:
#model Trinity.Models.tblUser
#{
ViewBag.Title = "Profile";
// prevent login by alterring adress
if (Session["userID"] == null)
{
Response.Redirect("~/Login/Index");
}
}
<h2>Let's fill you profile!</h2>
#using (Html.BeginForm("FillProfile", "Profile", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Sex, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Sex,
new List<SelectListItem> {
new SelectListItem { Value = "Male" , Text = "Male" },
new SelectListItem { Value = "Female" , Text = "Female" },
},
new {#class = "control-label col-md-2" })
#Html.ValidationMessageFor(model => model.Sex, "", new { #class = "text-danger" })
</div>
</div>
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Preffered, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Preffered,
new List<SelectListItem> {
new SelectListItem { Value = "Female" , Text = "Female" },
new SelectListItem { Value = "Male" , Text = "Male" },
new SelectListItem { Value = "Both" , Text = "Both" }
},
new {#class = "control-label col-md-2" })
#Html.ValidationMessageFor(model => model.Preffered, "", new { #class = "text-danger" })
</div>
</div>
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.BIO, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.BIO, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.BIO, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<label class="label-success">#ViewBag.SuccessMessage</label>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<label class="label-warning">#ViewBag.AlreadyRegisteredMessage</label>
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Model classes:
//-------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//-------------------------------------------------------------------------
namespace Trinity.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
public partial class tblUser
{
public int Id { get; set; }
[DisplayName("First Name")]
// [Required(ErrorMessage = "This field is required")]
[RegularExpression(#"^[^\W\d_]+$", ErrorMessage = "Only letters allowed in your name")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
// [Required(ErrorMessage = "This field is required")]
[RegularExpression(#"^[^\W\d_]+$", ErrorMessage = "Only letters allowed in your name")]
public string LastName { get; set; }
public string BIO { get; set; }
[DisplayName("E-Mail")]
// [Required(ErrorMessage = "This field is required")]
[RegularExpression(#"\w+([-+.']\w+)*#\w+([-.]\w+)*\.\w+([-.]\w+)*", ErrorMessage = "Incorrect E-mail Format")]
public string Email { get; set; }
[DataType(DataType.Password)]
// [Required(ErrorMessage = "This field is required")]
[RegularExpression(#"^.*(?=.{8,})(?=.*[\d])(?=.*[\W]).*$", ErrorMessage = "Password must be 8 characters, contain at least 1 digit and one special character")]
public string Password { get; set; }
[DisplayName("Confirm Password")]
[DataType(DataType.Password)]
// [Required(ErrorMessage = "This field is required")]
[Compare("Password", ErrorMessage ="Passwords are not the same")]
public string ConfirmPassword { get; set; }
public string Photo { get; set; }
[DisplayName("Age")]
// [Required(ErrorMessage = "This field is required")]
public int Age { get; set; }
public string Sex { get; set; }
public string Preferred { get; set; }
}
}

Update password for user account in mvc 5

I'm currently working on user account and so far i've managed to work upon register and login panel with email confirmation process. Here i'm stuck in forget password option. I'm using ado.net entity framework. All i have to do is to change password for the registered email. This is what i've done so far.
Note: getting the error in controller action method
the entity type is not part of the model for the current context
Edit
I have a table named registration attached to the DBContext class. And i'm trying to update the records (particularly the password field) for the forger password option. I made the property class UpdateAcccount.cs with validation as attached below. In order to update the password, I retrieved the row matching with email id. And then transferring the updated password in the database.
This time i'm getting the error of "password does not match" although there's no field of confirm password in the database(registration table) + i even tried to use bind(exclude) attribute for confirm password but that didn't work either.
Controller class
[HttpPost]
public ActionResult UpdateAccount(UpdateAccount account)
{
string message = "";
bool status = false;
if (ModelState.IsValid)
{
account.Password = Crypto.Hash(account.Password);
account.ConfirmPassword = Crypto.Hash(account.ConfirmPassword);
using (TravelGuide1Entities entity = new TravelGuide1Entities())
{
try
{
var v = entity.Registrations.Where(a => a.Email == account.Email).FirstOrDefault();
if (v != null)
{
v.Password = account.Password;
entity.Entry(v).State = System.Data.Entity.EntityState.Modified;
entity.SaveChanges();
return RedirectToAction("Login");
}
}
catch(Exception e)
{
}
}
}
return View();
}
UpdateAccount.cs
public class UpdateAccount
{
[Display(Name = "Email")]
[Required(AllowEmptyStrings = false, ErrorMessage = "Email id required")]
public string Email { get; set; }
[Display(Name = "New Password")]
[Required(AllowEmptyStrings = false, ErrorMessage = "Password required")]
[DataType(DataType.Password)]
[MinLength(6, ErrorMessage = "Minimum 6 character required")]
public string Password { get; set; }
[Display(Name = "Confirm Password")]
[DataType(DataType.Password)]
[Compare("Password", ErrorMessage = "Password do not match")]
public string ConfirmPassword { get; set; }
}
UpdateAccount.cshtml
#model Travel.Models.UpdateAccount
#{
ViewBag.Title = "UpdateAccount";
}
<h2>UpdateAccount</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>UpdateAccount</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Email, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Password, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Password, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Password, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ConfirmPassword, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ConfirmPassword, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ConfirmPassword, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}

Why is my form validation not firing?

I am building a Form in my view based on a View Model. the View model has [Required] on every field and Validation rules, but when checking the Model.IsValid it keeps coming back true even when all of the form fields are blank or null. Here is the View:
#model CommunityWildlifeHabitat.ViewModel.CreateAdminViewModel
#{
ViewBag.Title = "CreateAdmin";
}
<h2>Create Admin</h2>
#using (Html.BeginForm("CreateAdmin", "Admin", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "col-md-6 control-label" })
<div class="col-md-6">
#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.FirstName, new { #class = "col-md-6 control-label" })
<div class="col-md-6">
#Html.TextBoxFor(m => m.FirstName, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.FirstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.LastName, new { #class = "col-md-6 control-label" })
<div class="col-md-6">
#Html.TextBoxFor(m => m.LastName, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.LastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-6 control-label" })
<div class="col-md-6">
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Password, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmPassword, new { #class = "col-md-6 control-label" })
<div class="col-md-6">
#Html.PasswordFor(m => m.ConfirmPassword, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.ConfirmPassword, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-5 col-md-6">
<input type="submit" class="btn btn-habitat" value="Create Admin" />
</div>
</div>
}
Here is my ViewModel
public class CreateAdminViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { 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]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
Here is the Controller get and post
[Authorize]
public ActionResult CreateAdmin(int? id)
{
var model = new CreateAdminViewModel();
if (id == null) { return RedirectToAction("Index", "Communities"); }
if (User.Identity.IsAuthenticated)
{
var userId = User.Identity.GetUserId();
var superUser = db.CommunityTeams.Where(x => x.UserId == userId && x.RoleId == 4).Any();
// If User is not a SuperUser (Administrator)
if (superUser == false)
return RedirectToAction("Index", "Communities");
}
return View(model);
}
[Authorize]
[HttpPost]
public ActionResult CreateAdmin(FormCollection fc)
{
var model = new CreateAdminViewModel();
model.Email = fc["Email"];
model.FirstName = fc["FirstName"];
model.LastName = fc["LastName"];
model.Password = fc["Password"];
model.ConfirmPassword = fc["ConfirmPassword"];
if (ModelState.IsValid)
{ }
return RedirectToAction("index", "Admin");
}
On your HttpPost you have a model type of FormCollection
public ActionResult CreateAdmin(FormCollection fc)
Should it be of your model type? Else always true state?
public ActionResult CreateAdmin(CreateAdminViewModel fc)

how to add phone number field in registeration page of mvc application

i want to add phone number field on default registration page of MVC5 Web Application.
and when a user register with given info the user data store in default database of AspNetUsers table in PhoneNumber column.
here is my code for register View.
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmPassword, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.ConfirmPassword, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Number, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Number, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Register" />
</div>
</div>
}
and here is code for register view model.
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { 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(ErrorMessage = "Your must provide a PhoneNumber")]
[Phone]
[Display(Name = "Phone Number")]
public string Number { get; set; }
}
String number is also used in Manage.so i use the same name.but when a user register its phone number is not showing on database table.
All you need to do is include the phone number in the ApplicationUser like this:
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, PhoneNumber = model.Phone };
You can find that object in the
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, PhoneNumber = model.Phone };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
return View(model);
}
function in the AccountController.cs in a fresh MVC5 project. You can check the AspNetUsers table to see where it went.
It is probably wiser to not store your user data in that table, but create a seperate table for things like phone numbers, etc. That way you can keep your AccountController uncluttered and lean.
For the record, I just did a 'Create new project > MVC' in my VS2012. Nothing was changed from the default settings the new project came with except adding the Phone property to the model and an input field to the view, just like you did.

How can you pass a list of objects in a model back to a controller? [duplicate]

This question already has answers here:
Model Binding to a List MVC 4
(3 answers)
Closed 9 years ago.
UPDATE: The solution was to use an EditorTemplate. See solution below:
I want to pass a model to/from a controller which let's me set name, and set the value on an undetermined roles (as checkboxes). When I examine the postback, I get a value for Name in model, but Roles is null. How can I tell which checkboxes were checked?
Model:
public class MyModel
{
public string Name { get; set; }
public IEnumerable<RoleItem> Roles { get; set; }
}
public class RoleItem
{
public String Name { get; set; }
public String Id { get; set; }
public bool Selected { get; set; }
public RoleItem(String id, String name, bool selected = false)
{
this.Name = name;
this.Id = id;
this.Selected = selected;
}
}
Razor:
#model WebApplication1.Controllers.MyModel
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
#Html.TextBoxFor(m=>m.Name)
foreach (var m in Model.Roles)
{
<div>
#Html.Label(m.Id, m.Name)
#Html.CheckBox(m.Id, m.Selected, new { id = #m.Id })
</div>
}
<input type="submit"/>
}
GOAL: To allow any Administrator to add new users to the Asp identity tables and assign them roles that are defined in a list using checkboxes.
Model:
public class RegisterViewModel
{
[Display(Name = "Name")]
public string FullName { get; set; }
[Required]
[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; }
public List<RoleItem> Roles { get; set; }
}
public class RoleItem
{
public String Name { get; set; }
public String Id { get; set; }
public bool IsMember { get; set; }
}
Controller (GET): This reads all of the roles in the database and transforms them to a list of RoleItems. This will prepend the character "r" onto the id field as some browsers have a problem with an id starting with a number. We want to make sure the "Users" group is checked by default, so we find this value in the list and set the IsMember property to true. We check the request to see if the page was redirected here from a successful POST (see below)
// GET: /Account/AddUser
[Authorize(Roles = "Administrators")]
public ActionResult AddUser()
{
var rolesDb = new ApplicationDbContext(); //Users, Administrators, Developers, etc
ViewBag.AddSuccess = Request["added"]=="1" ? true : false;
var roleItems = rolesDb.Roles.Select(r => new RoleItem() { Id = "r" + r.Id, Name = r.Name, IsMember = false }).ToList(); //add an r to get around a browser bug
var users = roleItems.FirstOrDefault(r => r.Name == "Users"); //Get the row that has the Users value and set IsMember=true
if (users != null)
users.IsMember = true;
var m = new RegisterViewModel() {Roles = roleItems};
return View(m);
}
View: Pretty standard stuff. Note #Html.EditorFor(x => x.Roles) at the bottom, which uses an editor template (follows)
#model cherry.Models.RegisterViewModel
#{
ViewBag.Title = "AddUser";
}
<h2>#ViewBag.Title.</h2>
#if (Convert.ToBoolean(ViewBag.AddSuccess))
{
<text>User added!</text>
}
#using (Html.BeginForm("AddUser", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Create a new account.</h4>
<hr />
#Html.ValidationSummary()
<div class="form-group">
#Html.LabelFor(m => m.EmailAddress, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.EmailAddress, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.FullName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.FullName, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Password, new { value = Model.Password, #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmPassword, new {#class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.ConfirmPassword, new {value = Model.ConfirmPassword, #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Register" />
</div>
</div>
#Html.EditorFor(x => x.Roles)
}
EditorTemplate:
You MUST give the template the same name as the object you are creating the template for. You must also put this object in a folder called EditorTemplates below the view you are designing this for, or you can put the folder inside the shared folder.
Views\Account\EditorTemplates\RoleItem.cshtml
#model cherry.Models.RoleItem
<div>
#Html.CheckBoxFor(x => x.IsMember)
#Html.LabelFor(x => x.IsMember, Model.Name)
#Html.HiddenFor(x => x.Name)
</div>

Resources