- Overview
- Transcript
6.1 Editing and Deleting Users
Our post and tag functionality is pretty much complete. Now we focus on user management. We'll write a few helper classes, the user controller, and a class representing the data from the user form (a view model).
1.Introduction2 lessons, 05:56
1.1Introduction00:57
1.2What You Need04:59
2.Managing Posts3 lessons, 38:13
2.1First Steps07:55
2.2Writing the Views08:26
2.3Adding Functionality by Testing21:52
3.Tag Management2 lessons, 27:29
3.1Editing and Deleting Tags13:40
3.2Creating Tags13:49
4.Storing Data3 lessons, 47:22
4.1Setting Up the Dependencies17:58
4.2Implementing the Post Repository15:23
4.3Writing the Tag Repository14:01
5.Testing Functionality2 lessons, 33:06
5.1Testing the Post Functionality21:56
5.2Testing the Tag Functionality11:10
6.User Management3 lessons, 1:11:48
6.1Editing and Deleting Users25:19
6.2Creating Users21:53
6.3Managing a User's Roles24:36
7.Security and User Authentication2 lessons, 34:17
7.1Logging In and Out16:18
7.2Securing Our Application17:59
8.Enhancing the User Experience2 lessons, 36:38
8.1Incorporating a Menu19:51
8.2Adding JavaScript16:47
9.The Front-End2 lessons, 36:01
9.1Displaying the Published Posts24:37
9.2Paging Posts11:24
10.Conclusion1 lesson, 01:22
10.1Conclusion01:22
6.1 Editing and Deleting Users
For the most part we're done with managing posts in our tags. There are still some things we can do to enhance the user experience, and we will get to that, but at a later time. For now we need to focus on managing users, because that's essentially the last thing that we need in the admin section of our application. And because we are using identity, we are going to need easy access to a user manager. Because this is how we create users, and all of that wonderful stuff. But in order to get to a user manager, we have to create a context. And then the userStore, and then finally we can create the userManager. So, we can write a few classes that make this easier, so that if we only need a userManager, then all the other dependent objects are automatically created for us. But if we need a userStore, we can also create that and have the context automatically created. So let's go to our Solution Explorer and let's go to the data folder and we are going to add a new class. I'm going to call this IdentityClasses, and this is of course going to create a class called IdentityClasses. But I'm going to put multiple classes inside of this single file. This is something that I typically don't do, but in a case like this, It kind of makes sense to have all of this code together. So the first thing we need is an easy way of creating a userStore, so lets call this class CmsUserStore because this is specific to our application, and this is going to inherit userStore of CMS user. Of course we need a few using statements. One for our model's namespace, and then other for the userStore class. And we want two constructors. Now the first thing we want is, essentially, replacing the base constructor, which accepts a context object. So CmsUserStore, and we want a CmsContext object. And we can call this context, and this is going to call base. And it will pass in that context. And we don't have anything else that we need to initialize. So this is going to be an otherwise empty constructor. And then we need a default constructor, one where the context is automatically created for us. So we can do CmsUserStore, and then we can call this, and then new opp CmsContext. And that's basically all that we need to do with this class. That way we can create our userStore and automatically create our context. And we're going to essentially do the same thing for the userManager. So let's create a new public class called CmsUserManager. This is going to inherit from UserManager of CmsUser. We need a user statement for the UserManager class, so control dot then enter. And then we need some constructors. It's basically going to be the same thing that we did for the userStore, except that the first constructor is going to accept a userStore object, so this will be a userStore of CMS user, and let's call this parameter userStore, and this is going to call base and then pass in userStore, and there is nothing else that we need to initialize. And then we will have a default constructor, CmsUserManager. And this is going to new up a new, CmsUserStore. So now if all we need is a manager, we can just new up this CmsUserManager, and then we have our manager, and all of the dependencies are automatically created for us. So now that we have these classes, let's go back to our Solution Explorer. And inside of the admin area, we want to add a new controller. And let's call this UserController. Let's go ahead and add the two attributes at the controller level. We need the route area attribute, and that is admin. And we also need the route prefix attribute, which is going to be user. And the index action method is going to retrieve all of our users and display them within a table. So let's add the route attribute and we'll pass in an empty string, and we need to get all of our users, so we need a UserManager for that. So we can just create a user manager by newing up CmsUserManager. We need a using statement for that class, which is our data name space. And then we basically use this manager to retrieve all of the users. So if our users equals manager.users, and then ToList, or ToArray. Let's do ToList. And that is our model. So we can return the view and then pass in that list of users. And there's nothing else that we need to do inside of this action method, so we can get rid of that return statement. And let's go ahead and create this view. So let's right click. Let's add a view. The name is index. The template, we can have a list. And the model class is going to be our CmsUser. This is not going to be a partial view, but is going to use a layout page. And then instead of doing what we did with post, which in hind sight is probably something that we really didn't need to do, we will just use our admin layout, so let's go to the shared folder and let's choose underscore admin layout and then add. Now because we specified the CmsUser whenever we generated this view, Visual Studio only generated a table that displays all the details of CmsUser and that is our single property called DisplayName. So there's a few other things that would be helpful. There is the username, as well as the email address. So we can copy this, and we can paste it a couple of times, and then specify those values. So here it would be model.username, and then model.email. I think it's email. Now IntelliSense isn't going to help us much here, but if we go back to the user controller Ww can do users.First, and then let's see, email. It's just plain old email. So let's go back to the view, and we'll say model.Email. And then we need to add two more columns inside of the details part of the table. So we can take the same column, and then we can copy and paste. And then here we will specify those actual properties, so item.UserName and then Item.Email. And we will keep the edit and delete links, but we can get rid of the details because this is essentially the details. And whenever we go to edit, that's also going to show the details of the user. Let's go back to our user controller and let's add an extra method for editing a user. So public, action, result and this will be called edit. For the get request, all we need is the username, so we will accept that as our parameter. Let's go ahead and add the route attribute. So route and this is going to be edit, and then for our parameter variable, we'll just call that username. Now inside of this method, we need more than a manager. We also need the userStore. So we can create our userStore first. Let's called it userStore and we will just new up CmsUserStore. And then we will have another using statement and we'll call this userManager equals new CmsUserManager, and we will pass in the userStore. And the first thing we need to do is check to see if we have a user with the given username. So we can do var user = userStore, and we want to FindByName. Now once again, these methods are Async. And ideally, we would be doing all of this asynchronously. But we're going to stick with synchronous stuff for now. And we need to pass in the username And we want that result, so we will use the result property. Then we need to check to see if we have a user, because if we don't, then this particular resource does not exist. So, we can return an HttpNotFound. But if it does exist, then we need to pass that on to our view. So we will call view passing in our user. Although we need to return view, so return view. And that should be all that we need to do here. So let's create this view, so Add a view. The name is Edit, we will use the template of edit. And we will specify the model class of cms user, and we will use all the other default values. So we'll click on that, this is probably going to create a form with our single display name property. So we need to add more fields for the other pieces of data that we want to edit. Now we don't want to edit the user name, but we do want to be able to change the email address, as well as change the password. So in hindsight, using the CMS user class as the model for this view probably isn't the best solution. Because we're going to end up with different fields in the form than what we have as properties in CmsUser. Like, for example, in order to change a password, we need the current password. We also need the new password, as well as the confirmation password, and those are three fields that you will not find as properties on the CmsUser class. Now, there is the password property, but that is the encrypted password, that is not a clear text password, which is something that we would need for the form. So we are going to delete this view, and we will regenerate it after we have created a new model for this view. So we are going to essentially create a view model. Now this isn't the view model that you would have in the model view view model pattern. This is simply just a class that is going to be used as a model for a specific view. So, let's go to the admin folder because we don't really need this outside of the admin area. And let's add a new folder called View models and we are going to add a new class here. And ideally it would have some type of verb like edit user view model or create user view model, but as far as editing and creating, the view model itself is going to be very similar. Now, we're not going to use all of the properties between those two forms. Like, for example, the current password isn't going to be something that we would use in the create form. But we can still probably use the same class between both of those views. So let's just call this, user view model and if we need to change that later, then we can. So we need a property for the username and that is a string. We also need a property for the email address, which is another string. In fact, all of these are going to be strings. So next we need the current password, and then we need the new password, and then the confirmation password. And we can call that confirm password. So let's first of all create this new password and then the confirm password. Now the nice thing about having a view model now is that we can also add the validation attribute which is something that we couldn't do for the CMS user class. So for the user name, we will always need a user name so we can add the required attribute. And we can do the same thing for the email address. So, let's just go ahead and add that there. Now, for the current password, the new password, and the confirmed password, we don't really need the required attribute there. We will if we are creating a user, so that's something that we could add inside of the controller, at least as far as determining if NewPassword and ConfirmPassword have a value there. But we can use the compare attribute because we want to ensure that the value of NewPassword is the same as ConfirmPassword. So we specify that this property should be the same as ConfirmPassword. And for the error message, let's set error message equal to the NewPassword and ConfirmationPassword do not match, and that should be good for that. Let's add ConfirmationPassword there, and we should be good to go as far as As this ViewModel is concerned. So we can go back to our user controller. This also means that we're going to have to change this, but that's not that big of a deal, because we have our user object here. Now we need to just create a new UserViewModel. So let's do var viewModel equals, and we will new up UserViewModel and we can use initializer syntax to set our properties. So we can say user name equals user.UserName we can also set email equal to user.Email, and that's really all that we need to do we don't need to do anything with the password. So then we can pass our view model to the view, and that should work. So let's once again let's add a view, the template we will choose edit, and the model class will be our view model, and there it is. UserView model, and we will leave everything else the default values, and this will automatically generate everything that we need, but we do need to change the username, because this is something that we cannot change. So we could make this a hidden field, so let's just delete this div element for the user name and then somewhere inside of the form we will add a hidden field. So I'm going to do that after the antiforgery token. So HTML and then hiddenform, and then we will specify model, model.UserName. Then we have the email, we have the CurrentPassword, we have NewPassword, and then ConfirmPassword. Now we should change the EditorFor here. So instead of EditorFor, let's do PasswordFor. And we will then need to do that for the other password fields but there we go. That should be all that we need to do for this view. So we can go back to our controller, let's copy this. Although we need to add the HttpGet attribute to this method. And then we can copy all of this and then we will paste it and make modifications for post requests. So the first thing we need is to change that to HttpPost. The type of object is going to be our UserViewModel and let's just call this model. And then we can do the same thing that we did in the other edits method. We can check to see if the user exists, but here we need to use model.username. If it doesn't, then we return httpNotFound, otherwise we need to check the model state. So if not ModelState.IsValid then we want to return the view passing in the model. But if the ModelState does pass muster, then we have a username, we have an email address, and the new and confirm password fields do match. So first, we will check to see, if not, string IsNullOrWhiteSpace, model.new password and if this is true if we have a value here, then we need to ensure that we have a value for the current password. So if string IsNullOrWhiteSpace and then model.CurrentPassword, if this does not have a value, then we have an error because we are trying to change the password without the CurrentPassword. So we need to add something to the model state, we need an error. So AddModelError, we know that the string has to be empty here, so we'll specify that the current password must be supplied. And that's going to be fine for that. And after we add that model error, we need to return view. Passing in our model. So if we do have a value for the current password, we need to ensure that that password is what we have in the database. And there are many different ways that we can do this. We can use our userManager. There is a method called ChangePassword where we specify the user id, the current password and the new password. And then that would update the password for the user. But that is a single database interaction. We want to update the user all at the same time. This mean that we have to do everything manually. We have to manually verify that the current password equals what we have in the database. And if that is okay, then we need to manually hash the new password and manually assign that to our user. That sounds like a lot of work, but it's actually very easy to do. So the first thing we need to do is verify that our current password from the form matches what we have in the database. And we can do all of this in an if statement. But I'm going to break it up into creating a variable and then using that variable inside of an If statement because this particular statement is going to be fairly long. So let's create a variable called passwordVerified and we want to use our userManager object, and IntelliSense is not working, okay so let's see how good my typing is. We have a property called PasswordHasher and this is very useful, because there are a variety of things that we can use here, as far as checking to see if a password matches what we have in the data base, or matches a hashed password. Or we can hash a password, and so on, and so forth. So what we want here is a method called VerifyHashedPassword and we supply it two things. First is the hashed password, which is what we have from the database. So that is user.passwordhash and then the second thing is the clear text password that we want to compare it with. That is our model .currentpassword, and this does not return a poolian value. This is in E new which is why I was saying that this was going to be a rather long if statement, if we don't break this up into multiple lines of code. So we can use our password verified here and we want to compare this to an enum of type password verification result. Now without IntelliSense, this is going to hopefully be okay but this is in the Microsoft.AspNet.Identity name space and it's called PasswordVerificationResult. And there are several options. One is fail, I believe. The other is Success, which is what we want. And then the other is Success, but new hash needed, or something along those lines. Now let's change this to a not equal to, because if the current password from our form does not match what we have in the database, then we need to go back to the view and specifying that hey, the password you supplied is not the password we have in the database. So here if the password verified does not equal password verification result dot success, then we basically do what we did before, we just need to change the error for our model state error. So the current password does not match our records. And then we return the view, passing in the model. But if the verification does pass, then we need to hash our password. So we can create a variable called newHashedPassword, and we want to use our userManager once again, and the PasswordHasher object, and this has a method called Password or HashPassword. We'll find out. So Intellicence kind of works, but it doesn't. So we have this userManager.PasswordHasher.HashPassword and we simply provided the password that we want to hash. In our case, that is our model .NewPassword and then we assign that to our user objects password hash objects so PasswordHash = newHashedPassword and that's all that we need to do regarding the new password. So now we just to update the email address and the display name. So to do that, we just use user.Email = model.email and then user.DisplayName = model.DisplayName. And then we just need to update our user, now we don't have a display name property, did I not add that to the view model? No, I didn't. Okay, so, let's add that here, display name, and, this should be required as well, so we will add the required attribute to that property. And now we just need to update our user, and we do that with our user manager. Let's assign this to a variable called updateResult, and we want to use our userManager. This has a method called UpdateAsync, and we pass in our user. Of course we haven't been doing things asynchronously. So, we will use the result property to go ahead and run that synchronously. And then, we can check to see if the update result succeeded. So, there's a property called succeeded, and if that is true then we will simply return a redirect to action, and we want to go to the index to action. Otherwise, we need to go back to the view because something went wrong we dont know exactly what went wrong because if we've gotten this far then all the data from our forum is correct. So we can take this line of code where we add a model error to our model state, and we can just say that we don't know what happened. An error occurred. I guess that's the more political way of doing it. And please try again. And so I believe that's everything that we need to do inside of this edit method. Now since we did add a display property to our view model, we need to go back to our view, and we need to add a Field for the display name. So we can copy what we have for the email address and let's make the display name the first item. Then we just need to change the properties here, so this is now display name. And this email is DisplayName and this email is DisplayName. I think that that's all we need to change here in the view, so let's go back to our controller. We need to add the validate anti-forgery token attribute to our edit method. So ValidateAntiForgeryToken, and then we just need to write our delete method. And we're only going to do one for post requests. Because we're going to be making requests to the delete actions on all of our controllers using JavaScript. And, yes the correct thing to do would be to also have views for those for the browsers that either don't support Java Script or where the users have turned off Java Script. But, really who turns off Java Script in todays day and age? Java Script is so prevalent and is so needed. I just don't think that that's going to be an issue. If it is than well, tough. Okay, so let's just copy the edit method and let's make a few modifications. The route is delete and then username, HTTP post, anti forgery. The name is going to be delete and we don't have a user view model as our parameter instead we have our user name. Now we do need to use the user store to check to see if we have a user with the given username, so we will use our username here. If the user is null, then we return httpNotFound(). Otherwise, we need to delete the user, so we need to get rid of all of this code and then we will use our user manager. So this is going to return a result to the variable of deleteResult and userManager.DeleteAsync, and we pass in our user, and this is Async so we are going to use the result property to make it synchronous. Now we should check to see if this delete operation was successful. But, let's just return a redirect to action and index. Now we're not quite done because we need the ability to create users. And we're going to do that in the next lesson, but we're also going to incorporate roles, because we want to add some more security. We don't want just anyone to be able to create users. Or edit other people's posts. So we are going to create users as well as add users to rolls. And then we will modify our edit view and our edit action method, to handle editing the rolls of the users as well.