Register _LoginPartial if(Request.IsAuthenticated) - asp.net-mvc

I'm hitting a dead end with this one. I'm new to MVC and I see similiar issue appearing on SO, but it does not help. I'm basically trying to set a Register action, and once user is registered _LoginPartial view should indicate that the user is authenticated. From various posts and articles I read I believe I'm missing the element of storing this user to the cookie, but I do not know how to achieve this. I will appreciate any hints.
Controler:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
int userId = WebSecurity.GetUserId(model.UserName);
string roleName = ConfigurationManager.AppSettings["CustomerRole"];
if (!Roles.RoleExists(roleName)){ Roles.CreateRole(roleName); }
Roles.AddUserToRole(model.UserName, roleName);
var customer = new Customer();
customer.UserId = userId;
customer.Name = model.Name;
customer.PrivateEmail = model.PrivateEmail;
_customerRepo.Add(customer);
_customerRepo.SaveChanges(); // Customer, Membership, UserProfile added to db, no problems
TempData["Message"] = "User was registered"; // shows
return RedirectToAction("Index", "Home"); // shows
It seems everything is being saved correctly but the partial view does not see this user anymore...
_LoginPartial view
#if(Request.IsAuthenticated) {
<text> user: #Html.ActionLink(User.Identity.Name, "Manage", "Account",routeValues: null, htmlAttributes: new { #class = "username", title = "Change Password" })
#using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" }))
{#Html.AntiForgeryToken()
Log off}
</text>
} else {
<ul>
<li>#Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
<li>#Html.ActionLink("Register", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
</ul>
}

You need to call Login() after the SaveChanges();
It should be something like
WebSecurity.Login(model.UserName, model.Password, false);
More information http://www.codeguru.com/csharp/.net/net_asp/mvc/using-simplemembership-in-asp.net-mvc-4.htm

Related

Adding a Roles Checkbox List to the Register Method of the ASP.NET MVC 5 Default Template

I know this might be a lot of code to look at it, but it seemed like it was necessary to share it. Thanks in advance for reading!
I am building an application starting with the ASP.NET MVC 5 default template. I want to add a checkbox list of Identity's ApplicationRoles to the Register action of the Account controller.
So, rather than just collect the first and last names, email, phone number, etc., I also want to supply a checkbox list of roles in the database.
I've added this to the RegisterViewModel (in AccountViewModels.cs):
[Required]
[Display(Name = "Roles List")]
public IEnumerable<SelectListItem> RolesList { get; set; }
I changed the Account controller's HttpGet Register action from this:
// GET: /Account/Register
public ActionResult Register()
{
return View();
}
to this:
// GET: /Account/Register
[HttpGet]
public ActionResult Register()
{
//Populate the roles checkbox list for the view
RegisterViewModel model = new RegisterViewModel
{
RolesList = RoleManager.Roles.OrderBy(r => r.Name).ToList().Select(r => new SelectListItem()
{
Text = r.Name,
Value = r.Name,
Disabled = (r.Name == "Admin" && !User.IsInRole("Admin"))
})
};
return View(model);
}
Finally, I updated the Account controller's HttpPost Register action to this:
// POST: /Account/Register
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model, params string[] rolesSelectedOnView)
{
if (ModelState.IsValid)
{
rolesSelectedOnView = rolesSelectedOnView ?? new string[] { };
var user = new ApplicationUser { FirstName = model.FirstName, LastName = model.LastName, PhoneNumber = model.PhoneNumber, UserName = model.Email, Email = model.Email};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var rolesAddResult = await UserManager.AddToRolesAsync(user.Id, rolesSelectedOnView.ToString());
if (!rolesAddResult.Succeeded)
{
ModelState.AddModelError("", rolesAddResult.Errors.First());
AddErrors(rolesAddResult);
return View(model);
}
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
ViewBag.Message = "A confirmation email has been sent to the address you specified. Please have "
+ "the person check their email and confirm their account. The account must be confirmed "
+ "from the confirmation email before they can log in.";
return View("Info");
//return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
The Register view looks (in part) like this:
#model MngiReferrals.Models.RegisterViewModel
#{
ViewBag.Title = "Register";
}
<h2>#ViewBag.Title.</h2>
#using (Html.BeginForm("Register", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Create a new account.</h4>
...removed...
<div class="form-group">
#Html.Label("Roles", new { #class = "col-md-offset-2 col-md-10" })
<span class="col-md-offset-2 col-md-10">
#foreach (var item in Model.RolesList)
{
<input type="checkbox" name="RolesList" value="#item.Value" class="checkbox-inline" />
#Html.Label(item.Value, new {#class = "control-label"})
<br />
}
</span>
</div>
This allows the Register view to render with the normal fields and the list of roles in the database. However, when I submit the form, it doesn't try to validate the roles list (even though I've marked it as [Required] in the view model. Furthermore, it returns me to the Register form with the fields filled in, but then the checkbox list of roles is no longer on the form.
Finally, if I try to submit the form again, it returns this error from the view:
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Line 51: #Html.Label("Roles", new { #class = "col-md-offset-2 col-md-10" })
Line 52: <span class="col-md-offset-2 col-md-10">
Line 53: #foreach (var item in Model.RolesList)
Line 54: {
Line 55: <input type="checkbox" name="RolesList" value="#item.Value" class="checkbox-inline" />
After making these changes, the user is no longer registered in the database, so I'm not sure I'm even ever making it to the HttpPost Register action.
I would appreciate it if someone could help me fill in the blanks on this problem. Thank you in advance!
UPDATE #1
I updated my code based on a previous answer by #StephenMuecke (see his comment below for the link). I am close, but it looks like I am not correctly capturing the selected checkbox values.
Here is what this looks like now.
RegisterViewModel (in AccountViewModels.cs):
public class RegisterViewModel
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
...more properties...
[Required]
[Display(Name = "Roles List")]
public IEnumerable<SelectListItem> RolesList { get; set; }
public RegisterViewModel()
{
RolesList = new List<ApplicationRoleRegisterViewModel>();
}
}
ApplicationRoleRegisterViewModel (new View Model for the ApplicationRoles)
public class ApplicationRoleRegisterViewModel
{
[Required]
public string Name { get; set; }
public bool IsSelected { get; set; }
public bool IsDisabled { get; set; }
}
HttpGet Account Register action:
// GET: /Account/Register
[HttpGet]
public ActionResult Register()
{
//Populate the roles checkbox list for the view
var model = new RegisterViewModel { RolesList = new List<ApplicationRoleRegisterViewModel>() };
var roles = RoleManager.Roles.OrderBy(r => r.Name);
foreach (var role in roles)
{
var roleVm = new ApplicationRoleRegisterViewModel
{
Name = role.Name,
IsSelected = false, // Since this is for a user that does not yet exist, this would initially be deselected.
IsDisabled = role.Name == "Admin" && !User.IsInRole("Admin")
};
model.RolesList.Add(roleVm);
};
return View(model);
}
HttpPost Account Register action:
// POST: /Account/Register
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { FirstName = model.FirstName, LastName = model.LastName, PhoneNumber = model.PhoneNumber, UserName = model.Email, Email = model.Email};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//populate the roles checkbox list
var rolesSelectedOnView = model.RolesList.ToList();
foreach (var role in rolesSelectedOnView)
{
var roleVm = new ApplicationRoleRegisterViewModel
{
Name = role.Name,
IsSelected = role.IsSelected,
IsDisabled = role.IsDisabled
};
model.RolesList.Add(roleVm);
};
var rolesAddResult = await UserManager.AddToRolesAsync(user.Id, rolesSelectedOnView.Select(r => r.Name).ToArray());
if (!rolesAddResult.Succeeded)
{
ModelState.AddModelError("", rolesAddResult.Errors.First());
AddErrors(rolesAddResult);
return View(model);
}
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
// Uncomment to debug locally
// TempData["ViewBagLink"] = callbackUrl;
ViewBag.Message = "A confirmation email has been sent to the address you specified. Please have "
+ "the person check their email and confirm their account. The account must be confirmed "
+ "from the confirmation email before they can log in.";
return View("Info");
//return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Register View (uses RegisterViewModel):
<div class="form-group">
#Html.Label("Roles", new { #class = "col-md-offset-2 col-md-10" })
<span class="col-md-offset-2 col-md-10">
#for (var i = 0; i < Model.RolesList.Count; i++)
{
#Html.HiddenFor(m => m.RolesList[i].Name)
#Html.CheckBoxFor(m => m.RolesList[i].IsSelected)
#Html.LabelFor(m => m.RolesList[i].IsSelected, Model.RolesList[i].Name)
<br />
}
</span>
</div>

MVC5 Web Application Scaffold - Account/Logoff - Why use HTTP Post?

I am trying to get my head around MVC 5 Web Application template, and I noticed that special attention is given to the security around the LogOff link.
In the scaffold template the "LogOff" link in the _LoginPartial.cshtml view sits inside an HTML form with an AntiForgeryToken in it, and is defined as a JS call to form's submit action, like so:
#if (Request.IsAuthenticated)
{
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", #class = "navbar-right" }))
{
#Html.AntiForgeryToken()
<ul class="nav navbar-nav navbar-right">
<li>
#Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
<li>Log off</li>
</ul>
}
}
With the corresponding action method Account/LogOff inside ActionController defined like so:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("Index", "Home");
}
My question is - what is the reasoning behind it? Why does the LogOff action require so much security protection? Why not just have this in the view,
#Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
#Html.ActionLink("Log Off", "LogOff", "Account", routeValues: null, htmlAttributes: new { title = "LogOff" })
And this in the controller:
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("Index", "Home");
}
What security hole would this create?
Thanks.
Please refer to this link: Logout: GET or POST?.
It will answer your question on why Post should be used in logout.

ASP.NET MVC Get id parameter to controller

I use in view
using (Html.BeginForm("Test", "AcceptStatement", "Account", FormMethod.Post, new { id = Model.StatementID }))
in controller:
public ActionResult AcceptStatement(int id)
but id parameter in controller ends up being a null value. How can I get the id parameter value into my controller with Html.BeginForm?
You're using the wrong overload of BeginForm. You are currently passing the following:
actionName: "Test"
controllerName: "AcceptStatement"
routeValues: "Account"
formMethod: FormMethod.Post
htmlAttributes: { id = Model.StatementID }
This is obviously wrong as it makes no sense. You probably wanted:
Html.BeginForm("AcceptStatement", "Account", new { id = Model.StatementID }, FormMethod.Post, null)
Using this overload.
Use the information in this:
Basically, your view:
#using (Html.BeginForm()){
<p> Title: #Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /></p>
}
</p>
With a Controller of:
public ActionResult Index(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
An alternative to passing the model's value to controller method would be to use:
#Html.ActionLink("Details", "Details", new { id=item.BayID })
With your controller method being something like:
public ActionResult Details(int? id)
{
...

MVC5 Application: Create Identity User results in Invalid Model State?

I am upgrading an application in Development from MVC4/EF5 to MVC5/EF6 to make use of (among other things) ASP.Net Identity. When I try to Create a User, my code is flagging the Model as Invalid and not creating the user. My View is simply displaying a box to enter an email, and then a Switch that lets the logged in admin select either an MemberOrganization or Sponsor to assign the new user 2 via some dropdowns.
The Create() method of my UserController is below:
// GET: Admin/UserManagement/Create
public ActionResult Create()
{
ViewBag.headerTitle = "Create User";
ViewData["Organization"] = new SelectList(db.MemberOrganizations, "Id", "Name");
ViewData["Sponsor"] = new SelectList(db.SponsorOrganizations, "Id", "Name");
ViewBag.SwitchState = true;
ApplicationUser newUser = new ApplicationUser();
newUser.RegisteredDate = DateTime.Now;
newUser.LastVisitDate = DateTime.Now;
newUser.ProfilePictureSrc = null;
return View(newUser);
}
// POST: Admin/UserManagement/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "Property1, Property2, etc.")] ApplicationUser applicationUser)
{
if (ModelState.IsValid)
{
ViewBag.headerTitle = "Create User";
PasswordHasher ph = new PasswordHasher();
var password = ph.HashPassword("aR#nD0MP#s$w0r9");
var user = new ApplicationUser() { UserName = applicationUser.UserName, Email = applicationUser.Email, PasswordHash = password };
IdentityResult result = await UserManager.CreateAsync(user, user.PasswordHash);
if (result.Succeeded)
{
await db.SaveChangesAsync();
return RedirectToAction("Index", "UserManagement");
}
else
{
ModelState.AddModelError("", "Failed to Create User.");
}
}
ModelState.AddModelError("", "Failed to Create User.");
var errors = ModelState.Where(x => x.Value.Errors.Count > 0).Select(x => new { x.Key, x.Value.Errors }).ToArray();
var errors2 = ModelState.Values.SelectMany(v => v.Errors);
ViewData["Organization"] = new SelectList(db.MemberOrganizations, "Id", "Name", applicationUser.MemberOrgId);
ViewData["Sponsor"] = new SelectList(db.SponsorOrganizations, "Id", "Name", applicationUser.SponsorOrgId);
if (applicationUser.MemberOrgId != null)
{
ViewBag.SwitchState = true;
}
else
{
ViewBag.SwitchState = false;
}
ViewBag.OrganizationId = new SelectList(db.MemberOrganizations, "Id", "State", applicationUser.MemberOrgId);
// If we got this far, something failed, redisplay form
return View(applicationUser);
}
In my attempts to debug the issue I added the errors/errors2 variables as suggested in this post. Going down into the Model State properties when these are flagged I receive:
Does anyone have some thoughts on this matter? My previous code was working fine but I'm still getting use to ASP.Net Identity.
EDIT: As suggested by Rikard I have set my model where SponsorOrgID and MemberOrgID are not both required (only 1). Now my code processes down until the following segment:
var user = new ApplicationUser() { Name = applicationUser.Name, Email = applicationUser.Email, PasswordHash = password };
IdentityResult result = await UserManager.CreateAsync(user, user.PasswordHash);
if (result.Succeeded) // ERROR
{
await db.SaveChangesAsync();
return RedirectToAction("Index", "UserManagement");
}
When I check the value of result and drill down to Errors->[string[]]->[0] the error message is: Name cannot be null or empty. Anyone have thoughts on this? I added a field to my View to specify the new users Name and incorporated it into the above new ApplicationUser() code line. I'm not fully sure where I am missing something.
EDIT2:
Create() View [Relevant]:
#model PROJECTS.Models.ApplicationUser
#{
ViewBag.Title = "Create";
Layout = "~/Areas/Admin/Views/Shared/_LayoutAdmin.cshtml";
string cancelEditUrl = "/Admin/UserManagement/";
}
#using (Html.BeginForm("Create", "UserManagement", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.RegisteredDate)
<div class="container">
<div class="row">
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field" style="margin-bottom: 15px">
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="row">
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field" style="margin-bottom: 15px">
#Html.TextBoxFor(model => model.Email, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Email)
</div>
</div>
....
As you can se in your last picture you have an error on the property SponsorOrgId that has the value string.Empty (""). Maybe the SponsorOrgId in ApplicationUser has the [Requried] attribute.
EDIT
Regarding your problem when trying to add the user to the Database (that was happen when you call UserManager.Create(user,password);
IdentityResult result = await UserManager.CreateAsync(user, user.PasswordHash);
if (result.Succeeded)
{
await db.SaveChangesAsync();
return RedirectToAction("Index", "UserManagement");
}
else
{
var errors = string.Join(",", result.Errors);
ModelState.AddModelError("", errors);
}
Then you can debug the value of "errors" or read the error message from your ModelState.
Regarding your EDIT
Add name to this part:
var user = new ApplicationUser() { UserName = applicationUser.UserName, Email = applicationUser.Email, PasswordHash = password, Name = applicationUser.Name };
EDIT 2
The problem is that is not possible to create a user without a username. But you can add the user's email to the username. And then change it to the user specified username. To make it pass the validation you need to add this part.
UserManager.UserValidator = new UserValidator<User>(UserManager) { RequireUniqueEmail = true };
I realize it's late for a reply, but I read four threads on this before resolving this issue. It's not entirely obvious, and it appears to be an inheritance conflict with custom properties. The root of my problem was my creation of a UserName property - a custom property (...or so I thought) that I wanted to define as FirstName + " " + LastName.
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
// public new string UserName { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
...remainder removed for clarity.
As soon as I commented-out this line from IdentityModels.cs, which removed my custom property, POOF! Problem: solved. I drilled inheritance definitions all the way back to IUser[TKey], and I found what I think was the root of my (our) problem.
namespace Microsoft.AspNet.Identity
{
// Summary:
// Minimal interface for a user with id and username
//
// Type parameters:
// TKey:
public interface IUser<out TKey>
{
// Summary:
// Unique key for the user
TKey Id { get; }
//
// Summary:
// Unique username
string UserName { get; set; }
}
}
I knew you could add custom properties to the ApplicationUser class, but I didn't know there were specific property names already in use - not defined in this class. Initially, after adding my UserName field simply as a public string, I received this:
[...] warning CS0114: 'MyApp.Models.ApplicationUser.UserName' hides inherited member 'Microsoft.AspNet.Identity.EntityFramework.IdentityUser<string,Microsoft.AspNet.Identity.EntityFramework.IdentityUserLogin,Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole,Microsoft.AspNet.Identity.EntityFramework.IdentityUserClaim>.UserName'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
I'm good at following instructions, so I added the 'new' keyword (remember that line above I had to comment-out...?). It solved the compile time CS0114 warning, but it obstructed IUser's UserName.
OP (and countless others) did the same thing, I believe, when he wrote this:
var user = new ApplicationUser() { UserName = applicationUser.UserName, Email = applicationUser.Email, PasswordHash = password };
If you have username and email properties in your applicationuser class, these properties are hiding actual properties, so remove them from your application class.This will solve the problem.

getting actionlist to send current url to actionresult

I'm trying to pass my current url to my actionresult but returnUrl always comes up null
#Html.ActionLink("Create Student", "Create", "StudentCenter", new{ returnUrl = Request.RawUrl}, new { #class = "button" })
here's the controller
public ActionResult Create(string returnUrl)
{
HttpCookie cookie = new HttpCookie("redirectCookie",returnUrl);
ViewBag.Sex = new SelectList(Enum.GetNames(typeof(SchoolCup.Domain.Sex)));
return View();
}
I think you have to use like this
#Html.ActionLink("Create Student", "Create", "StudentCenter", new{ returnUrl = Request.RawUrl.ToString()}, new { #class = "button" })
Please try and send me a feed back
Is your expected string like this "studentCenter/create"?

Resources