问题
I have a dynamic in line form in my ASP.NET MVC application, which when a user clicks a button, in this case 'add', it will add a new row to the form with the required fields. I am trying to get this to work with ASP.Net Identity, but not having any luck.
I believe it would be similar to having a list, and then doing a for a loop through the list to register the users, but putting it into practice is proving to be confusing.
<input name="__RequestVerificationToken" type="hidden" value=""> <div class="row">
<div class="form-group mx-1">
<label class="control-label" for="Email">Email</label>
<input name="Email" class="form-control" data-val="true" data-val-email="The Email field is not a valid e-mail address." data-val-required="The Email field is required." id="Email" placeholder="Email" type="text" value="">
</div>
<div class="form-group mx-1">
<label class="control-label" for="User_Role">User Role</label>
<select name="UserRoles" class="form-control" data-val="true" data-val-required="The UserRoles field is required." id="UserRoles" ><option value="">Role</option>
<option value="Employee">Employee</option>
<option value="Manager">Manager</option>
</select>
</div>
<div class="form-group mx-1">
<label class="control-label" for="Password">Password</label>
<input name="Password" class="form-control valid validate-equalTo-blur" data-val="true" data-val-length="The Password must be at least 6 characters long." data-val-length-max="100" data-val-length-min="6" data-val-required="The Password field is required." id="Password" placeholder="Password" type="password" value=""aria-describedby="Password-error" aria-invalid="false">
</div>
<div class="form-group mx-1">
<label class="control-label" for="ConfirmPassword">Confirm password</label>
<input class="form-control valid" name="ConfirmPassword" data-val="true" data-val-equalto="The password and confirmation password do not match." data-val-equalto-other="*.Password" id="ConfirmPassword" placeholder="Confirm Password" type="password" value="" aria-describedby="ConfirmPassword-error" aria-invalid="false">
</div>
<button type="button" class="btn btn-sm btn-info add_button form-control col-md-1" style="margin-top: 37px"><i class="fas fa-plus"></i></button>
<div class="field_wrapper">
<div class="form-group row">
<fieldset class="form-group mx-1">
<label class="control-label" for="Email">Email</label>
<input name="Email" class="form-control" data-val="true" data-val-email="The Email field is not a valid e-mail address." data-val-required="The Email field is required." id="Email" placeholder="Email" type="text" value="">
</fieldset>
<fieldset class="form-group mx-1">
<label class="control-label" for="User_Role">User Role</label>
<select name="UserRoles" class="form-control" data-val="true" data-val-required="The UserRoles field is required." id="UserRoles" ><option value="">Role</option>
<option value="Employee">Employee</option>
<option value="Manager">Manager</option>
</select>
</fieldset>
<fieldset class="form-group mx-1">
<label class="control-label" for="Password">Password</label>
<input name="Password" class="form-control" data-val="true" data-val-length="The Password must be at least 6 characters long." data-val-length-max="100" data-val-length-min="6" data-val-required="The Password field is required." id="Password" placeholder="Password" type="text" value="">
</fieldset>
<fieldset class="form-group mx-1">
<label class="control-label" for="ConfirmPassword">Confirm password</label>
<input name="ConfirmPassword" class="form-control" data-val="true" data-val-equalto="The password and confirmation password do not match." data-val-equalto-other="*.Password" id="ConfirmPassword" placeholder="Confirm Password" type="text" value="">
</fieldset>
<a href="#" class="btn btn-sm btn-danger remove_button form-control col-md-1" style="margin-top: 37px"><i class="fas fa-times center"></i></a>
</div>
</div>
<div class="row">
<button type="submit" class="btn btn-info my-2">Submit</button>
<a type="button" class="btn btn-warning my-2 ml-1" href="/Home">Back</a>
</div>
Jquery Form:
$(document).ready(function () {
var max_fields = 10; //maximum input boxes allowed - change as needed
var wrapper = $(".field_wrapper"); //Fields wrapper
var add_button = $(".add_button"); // class add button
var remove_button = $('.remove_button'); // class remove button
var html = `
<div class="form-group row">
<fieldset class="form-group mx-1">
@Html.LabelFor(m => m.Email, new { @class = "control-label" })
@Html.TextBoxFor(m => m.Email, new { @class = "form-control", placeholder="Email" })
</fieldset>
<fieldset class="form-group mx-1">
@Html.Label("User Role", new { @class = "control-label" })
@Html.DropDownList("UserRoles", (SelectList)ViewBag.Name, "Role", new { @class = "form-control"})
</fieldset>
<fieldset class="form-group mx-1">
@Html.LabelFor(m => m.Password, new { @class = "control-label" })
@Html.TextBoxFor(m => m.Password, new { @class = "form-control", placeholder="Password" })
</fieldset>
<fieldset class="form-group mx-1">
@Html.LabelFor(m => m.ConfirmPassword, new { @class = "control-label" })
@Html.TextBoxFor(m => m.ConfirmPassword, new { @class = "form-control", placeholder="Confirm Password" })
</fieldset>
<a href="#" class="btn btn-sm btn-danger remove_button form-control col-md-1" style="margin-top: 37px"><i class="fas fa-times center"></i></a>
</div>`;
var x = 1; //initlal count
$(add_button).click(function (e) { //on add button click
e.preventDefault();
if (x < max_fields) { //max input box allowed
x++; // increment value of x
$('.counter').text(x);
$(wrapper).append(html); //add html
}
});
$(wrapper).on("click", remove_button, function (e) { // runs when a user clicks on anything with the class 'remove_button'
e.preventDefault(); // prevent default, duh
$(this).parent('div').remove(); // get parent of each element and remove it
x--; // decrement the value of x
$('.counter').text(x); // update text with the count only after value of x has been changed
})
});
My AccountController method - I have an employee class which inherits the identity model and also uses its own view model:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RegisterEmployees(EmployeeViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
回答1:
I'm pretty sure the previous answer to this would work, but you commented on one of my other answers asking to look at this question, so here you go:
I've tried to keep it as simple as possible, so you're gonna have to add in your own extra markup, styling, data annotations etc.
Models
Pretty self explanatory, we just have a model for the view that has a list of user models.
public class RegisterLotsModel
{
public List<UserToRegister> UsersToRegister { get; set; }
}
public class UserToRegister
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
View
Just a simple form with some inputs for the fields to register the users. The surrounding divs are referenced in the JS and are used when we append the HTML for the extra users.
@model BulkRegister.Models.RegisterLotsModel
@using (Html.BeginForm("BulkRegister"))
{
<div class="users-to-register">
<div class="user-container">
<input type="text" name="UsersToRegister[0].UserName" />
<input type="email" name="UsersToRegister[0].Email" />
<input type="password" name="UsersToRegister[0].Password" />
</div>
</div>
<input type="button" id="add-user-button" value="Add another user" />
<input type="submit" value="Register the users" />
}
@section scripts {
<script src="~/Scripts/bulk-reg.js"></script>
}
JQuery
I put this in a separate file, but you can just pop it at the bottom of the view if you like. We count how many user-container classes are already on the page to work out what the index for the HTML we're adding should be. This means we don't have to keep track with a separate variable.
$(function () {
$('#add-user-button').click(function () {
// how many users are there already
var userContainerCount = $('.user-container').length;
// this count is used for the index for the next user container
var nextIndex = userContainerCount;
// build up some html. it's far simpler to just use regular html here, rather than razor.
// if you really want to use razor then look into partial views and fetching via ajax.
// you could use es6 backticks here to create a multi line string, but there seemed to
// be a little confusion in the comments, so this is just a plain concatenated string.
var html = '<div class="user-container">' +
'<input type="text" name="UsersToRegister[' + nextIndex + '].UserName" />' +
'<input type="email" name="UsersToRegister[' + nextIndex + '].Email" />' +
'<input type="password" name="UsersToRegister[' + nextIndex + '].Password" />' +
'</div>';
// append the html
$('.users-to-register').append(html);
});
});
Controller Actions
GET: Nothing fancy.
public ActionResult BulkRegister()
{
return View();
}
POST: Loop over the users in the model and just register them with the UserManager. You'll need to add validation and whatever you want to do to report successes and failures.
[HttpPost]
public async Task<ActionResult> BulkRegister(RegisterLotsModel model)
{
// do whatever validation you want
if (!ModelState.IsValid)
return View(model);
// loop over your users and register them in the regular way
var successful = new List<string>();
var failed = new List<string>();
foreach (var toRegister in model.UsersToRegister)
{
var user = new ApplicationUser { UserName = toRegister.UserName, Email = toRegister.Email };
var result = await UserManager.CreateAsync(user, toRegister.Password);
if (result.Succeeded)
successful.Add(toRegister.UserName);
else
failed.Add(toRegister.UserName);
}
// do whatever you want to do with failures
if (failed.Any())
return RedirectToAction("FailedRegistration", new { failed });
// maybe register some more users?
return RedirectToAction("BulkRegister");
}
And that's it. If you've got a lot of users to register you might want to just build up the user models and insert them straight into the database in a single transaction.
回答2:
- Create a View model that will house the list of type UserViewModel. I used
EmployeeListViewModel;
public class EmployeeListViewModel{
public List<EmployeeViewModel> UsersToRegister {get;set;}
}
- Change the model declaration on the top of your view to;
@model EmployeeListViewModel
- Use this for your form. We need to define the input fields statically because we need to use the 0 index on the name like
Model.UsersToRegister[0].Email;
// please edit with your parameters
@Html.BeginForm(){
// add validate forgery token html helper
<div class="form-group mx-1">
<label class="control-label" for="Email">Email</label>
<input name="Model.UsersToRegister[0].Email" class="form-control" data-val="true" data-val-email="The Email field is not a valid e-mail address." data-val-required="The Email field is required." id="Email" placeholder="Email" type="text" value="">
</div>
<div class="form-group mx-1">
<label class="control-label" for="User_Role">User Role</label>
<select name="Model.UsersToRegister[0].UserRoles" class="form-control" data-val="true" data-val-required="The UserRoles field is required." id="UserRoles" >
<option value="">Role</option>
<option value="Employee">Employee</option>
<option value="Manager">Manager</option>
</select>
</div>
<div class="form-group mx-1">
<label class="control-label" for="Password">Password</label>
<input name="Model.UsersToRegister[0].Password" class="form-control valid validate-equalTo-blur" data-val="true" data-val-length="The Password must be at least 6 characters long." data-val-length-max="100" data-val-length-min="6" data-val-required="The Password field is required." id="Password" placeholder="Password" type="password" value=""aria-describedby="Password-error" aria-invalid="false">
</div>
<div class="form-group mx-1">
<label class="control-label" for="ConfirmPassword">Confirm password</label>
<input class="form-control valid" name="Model.UsersToRegister[0].ConfirmPassword" data-val="true" data-val-equalto="The password and confirmation password do not match." data-val-equalto-other="*.Password" id="ConfirmPassword" placeholder="Confirm Password" type="password" value="" aria-describedby="ConfirmPassword-error" aria-invalid="false">
</div>
<button type="button" class="btn btn-sm btn-info add_button form-control col-md-1" style="margin-top: 37px"><i class="fas fa-plus"></i></button>
}
- Update your script to use this, what we did here is we're using
xas a counter. The counter will indicate which index (in a list of objects) does a certain input field belong to;
<script>
var x = 1; //initlal count
$(add_button).click(function (e) { //on add button click
e.preventDefault();
if (x < max_fields) { //max input box allowed
var html = "
<div class='field_wrapper'>
<div class='form-group row'>
<fieldset class='form-group mx-1'>
<label class='control-label' for='Email'>Email</label>
<input name='Model.UsersToRegister["+x+"].Email' class='form-control' data-val='true' data-val-email='The Email field is not a valid e-mail address.' data-val-required='The Email field is required.' id='Email' placeholder='Email' type='text' value=''>
</fieldset>
<fieldset class='form-group mx-1'>
<label class='control-label' for='User_Role'>User Role</label>
<select name='Model.UsersToRegister["+x+"].UserRoles' class='form-control' data-val='true' data-val-required='The UserRoles field is required.' id='UserRoles' ><option value=''>Role</option>
<option value='Employee'>Employee</option>
<option value='Manager'>Manager</option>
</select>
</fieldset>
<fieldset class='form-group mx-1'>
<label class='control-label' for='Password'>Password</label>
<input name='Model.UsersToRegister["+x+"].Password' class='form-control' data-val='true' data-val-length='The Password must be at least 6 characters long.' data-val-length-max='100' data-val-length-min='6' data-val-required='The Password field is required.' id='Password' placeholder='Password' type='text' value=''>
</fieldset>
<fieldset class='form-group mx-1'>
<label class='control-label' for='ConfirmPassword'>Confirm password</label>
<input name='Model.UsersToRegister["+x+"].ConfirmPassword' class='form-control' data-val='true' data-val-equalto='The password and confirmation password do not match.' data-val-equalto-other='*.Password' id='ConfirmPassword' placeholder='Confirm Password' type='text' value=''>
</fieldset>
<a href='#' class='btn btn-sm btn-danger remove_button form-control col-md-1' style='margin-top: 37px'><i class='fas fa-times center'></i></a>
</div>
</div>
";
x++; // increment value of x
$('.counter').text(x);
$(wrapper).append(html); //add html
}
});
</script>
- Then use this for your controller action, we replaced the parameter with the new class;
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RegisterEmployees(EmployeeListViewModel model)
{
if (ModelState.IsValid)
{
foreach(var u in model.UsersToRegister){
var user = new ApplicationUser { UserName = u.Email, Email = u.Email };
var result = await UserManager.CreateAsync(user, u.Password);
if(!result.Succeeded){
break; // depends on you if you want to stop creating users if one failed
// continue; // depends on you if you want to continue creating users if one failed
}
}
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
来源:https://stackoverflow.com/questions/61377847/registering-multiple-users-using-same-form-net-identity