- Overview
- Transcript
6.3 Managing a User's Roles
We'll first spend some time going over the refactored code, but then we'll dive into managing a user's roles. We need to display the roles within the create/edit user forms, as well as assign the user to the selected role.
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.3 Managing a User's Roles
At the end of the previous lesson, I asked you to refactor the user related code. That is the UserRepository, the UserService and the UserController. The reason being, because we needed to make things asynchronous. And I supplied you with two sets of code. First was the code prior to the refactor and then the code after the refactor. And for the first few minutes of this lesson, we're going to go over that refactored code. Now don't worry if your code is different from mine. We are two different people, we think differently and we code differently. So yes, there are going to be some differences. There will probably be some similarities as well. And there are some things that I would normally do that I didn't do. Primarily, because I wanted to keep things simple and I wanted to avoid complexity as much as possible. So we're going to start with the user controller and we'll start with the Index action method. Now not much changed here. I'm using the userRepository to get all of the users and passing that onto the View. Now the reason why I'm using the userRepository is because our View depends on that CMS user class. Ideally, we would have the UserService return a collection of the UserViewModel. Because as you're going to see with all of the other action methods on this controller. Everything that we do, we're dealing with the UserViewModel as opposed to the actual user object from the database. But I chose to leave this as is just to simply save some time. So let's move on to the Create method. This is for get requests. And there's a tiny little variation here that's we are creating a model. And passing that model on to the View. Now before, we simply just called the View method. Now the reason why I did it this way is because later on in this lesson we're going to have to modify this model object. Because we need to supply the roles that are going to be listed in the form for creating a new user. So that's why that was changed. Then we have the creates method for post requests and it's a lot like what we had at the end of the previous lesson. Except that now we have this completed variable. And the only reason why I did it this way is so that I can set a break point on line 49, to ensure that code is working. And I can also inspect the value of completed. Normally, I would just have await. And then whatever statement after await inside of the if statement. But this way, it's a little bit easier to debug. Then we have the Edit method for get requests. It simply retrieves the user with the username and it uses the user service to do that. It checks to see if the user is null. If it is, then it returns HttpNotFound, otherwise it passes the user onto the View. Now the edit method for post requests has drastically changed. Instead of all those lines of code, we just have these few lines. We take the model from the user, we pass that on to the UserService to its UpdateUser method. If this was successful, then we redirect to the index action. Otherwise, we go back to the View passing on the model and there will be an error that will be displayed for the user. And then finally we have the Delete method. Simply accepts a username and then we pass that username onto the users services DeleteAsync method. And then we return a redirect to the index action. Let's look at the UserService now. We have this GetUserByNameAsync. It retrieves a user and returns that user into a UserViewModel object. It basically sets the UserName, the Email and the DisplayName and then it returns that viewModel. So nothing really spectacular there. Then we have the CreateAsync method where it accepts the model. It checks to see if the model state is valid. If it's not, it returns false. It also checks to see if there is an existing user with the given UserName. If there is, then an error is added to the modelState and then return false. And then, if the password is null or white space. Then another error is added to the modelState and return false. Then we create the user and we pass that user on to the UserRepository. To actually create the user in the database and then return true. Then UpdateUser, this is the code that was inside of the Edit method for post requests. So there's a lot going on here. First of all, we're checking to see if the user exists. And in this case, if it doesn't, then an error is added to the modelState. Ideally, I would return an enum instead of the boolean. Because an enum allows you to return more than two values. Like I would have an option for success and then another for not found. And then one for failed or something along those lines. And then inside of the controller, I could check to see if the return value was not found. And if so, then I can return an HttpNotFound. So it gives you a lot more flexibility that way, but it also adds a little bit of complexity. And in this case, I opted to be very simple and just return a boolean value. So if the user doesn't exist, then return false. If the modelState is not valid, return false. And then the password is checked. Now, whenever we attempt to set a new password. We needed to verify that the password that was in the form is the same as the password that's in the database. And in order to do that, we had to use that usermanager object. Well inside of the UserService, we don't have that user manager object we have a user repository. So on the UserRepository I had to add a new method called the VerifyUserPassword. This of course, returns a boolean value. So if the password was not verified, then an error is added to the modelState and then return false. And then, if it gets to this point, we'll need to hash the password. So I had to add another method to the repository to hash the password. Because if you remember, hashing a password involves that user manager object. So there is a method on the repository to hash the password. It assigns that to this newHashedPassword and then that is set as the PasswordHash on the user. Then the user Email is updated, the DisplayName is updated and then the updated user is written to the database. Then for the DeleteAsync method, it accepts the username. It checks to see if the user exists. If not, then it simply returns. Otherwise, it deletes the user. And so finally, the UserRepository. Let's start with the HashPassword method. It simply uses our user manager and its PasswordHasher property and it hashes that password. And then here's the VerifyUserPassword method. It accepts the hashedPassword and then the providedPassword. Now if you remember that VerifyHashedPassword returns an enum. Because there are multiple values that could be the result of verifying a hashedPassword. In this case, I only cared if it was the same or not. So I simply use a conditional to check to see if VerifyHashedPassword is equal to Success. If so, then great, it's true. Otherwise, it's false. And I don't think there's any new methods here. We have GetUserByNameAsync. We have GetAllUsersAsync. We have CreateAsync. We have DeleteAsync, UpdateAsync and then the two new methods. Now I also modified the RoleRepository. So if we go and look at the RoleRepository, everything here is asynchronous as well. So we have GetRoleByNameAsync, we have GetAllRolesAsync and then CreateAsync, and these changes made it so that I had to modify the AuthDbConfig file. Because if you'll remember, we also seeded the database with our three roles. So now I just simply had to use the await keyword in conjunction with these asynchronous methods and then everything is fine. Now, with all of that out of the way, we need to focus on managing a user's roles, and we do that whenever we create a user and when we edit them, so the first thing we need to do is modify our view module, because that is essentially the delivery mechanism for getting data to our create and edit forms, so let's open up that file. And the first thing we need are the roles that we are going to display in a drop down box and we can do that by supplying some type of collection of select list item. This is a special class that we can use for generating a drop down list. So let's add a property, public. And we can make this in IEnumerable of SelectListItem, we need a using statement for system.web.mvc and this is going to give us an issue as far as name spaces are concerned. Because the system.web.mvc namespace also has a compare attribute. So we need to explicitly specify what compare attribute that we want to use. And that is the System.ComponentModel.DataAnnotations.Com- pare. So that will fix that particular issue. And we can call this simply Roles. And we will have a getter that will return a new SelectList. This is basically an IEnumerable of select list items, and each item is of course an item in the drop down box. Now we don't have a data source for this select list. So we could add a private read-only. List of string which will contain all of the names of our roles and then we can have a method to load these roles. So let's go ahead and initialize this as a new list of string. And then we can pass _roles to our SelectList. And then that is going to create a list for creating a drop down box. But then we also need a method for loading the roles. So let's just call this LoadUserRoles. And we want to accept an IEnumerable of IdentityRole. And we need the using statement so that we can use that class, and let's just call this parameter, roles. And then we want to load our _roles with the values provided here. So we can do _roles.AddRange. And then we could use a link expression for roles.Select. We only want the names here, we don't need the ids. Because whenever we use the _role API, we do so based upon the role names. So here we can select the name and that's really all that we need to select here. That is going to pass that collection on to the AddRange method, which will then be used for the SelectList. So now the only other thing that we need is a way of keeping track of these selected roles. So let's add another public field, it will be a string and let's call this SelectedRole. And then whenever we display the dropdown list, we will specify that this is the property for keeping track of the selected role. So let's go ahead and let's also add some display attributes here. So for the SelectedRole property we want. The display, let's set the name equal to role. And I did that completely wrong. So name equal role. We don't need to do anything for our roles collection because the SelectedEole is going to be used for displaying the name of the field. Now, we do need a name for the, ConfirmPassword. So let's just change this to ConfirmPassword. And we also need to do the same thing for the new password field. So let's add that, and let's change it to New Password. The Current Password can be Current Password we just need to change that so that we have a space between Current and Password. Display name will follow the same pattern as well as user name. I don't think we need Display Attribute for email because email is email but we do need one for the Display Name and the User Name. And then once we finish this, we can move over to the UserController and we can start with the Create method because we need to populate our roles collection with the roles from the database. So we're done with our View model, we can close that. Let's go to the UserController and the Create method. And we have already created a model here, so basically we just need to load the roles. So we can do model.LoadUserRoles. And we need to get the roles from the database, and we have that with our role repository. But this is asynchronous. So we need to use the await keyword_roleRepository and then GetAllRolesAsync. That's going to retrieve all of the roles as an IEnumerable of identity role and pass that on to LoadUserRoles. And then we need to go to the other Create method. Although we're not going to do anything inside of this method because all of the work is being done inside of the User Service. So let's open up the User Service. And we need to go to the CreateAsync method. Because that is where we are going to add a user to a role. In fact after we create the user, that's the perfect place to then add the user to a role. But in order to do that, we need to use a user manager object. And since we are inside of the user service, we don't have that ability. But we could add a method to our user repository to add a user to a role. Inside of the user service we could do something like this. We could await_users then we could call this method AddUserToRoleAsync. We would need to supply the newUser as well as the SelectedRole and we have that with our model. So let's go ahead and add this to our interface. We can hit control dot. And then enter and that is going to add that to the IUserRepository. In fact, if we go over here we can see that now we have an AddUserToRoleAsync that accepts a CmsUser. And a string of p. Let's call this role. And then we need to implement this method. So let's go to our UserRepository, that's inside of our Data folder. And we can add this method anywhere, but I'm going to add it before the Dispose method, because this is a new method. So public async, we don't have a return value, so it's just gonna be Task. Then AddUserToRoleAsync. The two type of parameters are CmsUser, and we can call that user. And then string role. And then basically we want to use the user manager, so _manager, this has a method called AddToRoleAsync, and we simply supply the user ID, so user.Id, and then the role. And this is why we created the user first, because we have to have the Id for that user. So we create the user first, then we add the user to the role. And that's really all we should do for the creation of a user. Now, editing a user is going to be a little bit more involved. So let's go to our UserController. Let's go down to the Edit method for get requests. And we want to do something whenever we are retrieving the information for a user, because this is where we also need to get the role that a user is a part of. And we would do that inside of this GetUserByNameAsync. So let's go to that definition. And we want to modify this method. Now it's okay to create our view model, but after we create the view model, we want to get the role. So first of all, we need the ability to get the roles for a user. And we have that ability for with a user manager. So once again, we are going to have to add another method to our user repository. But for right now, we could write code like this. We can call this variable userRoles, and then we would await _users and we would call this method GetRolesFor UserAsync and then we could supply the user. So, pass in user. Let's go ahead and let's generate this method on our interface. And we're going to need to go to that interface and change this from returning a task of object, to a task of IEnumberable of the string. Then we need to implement this method on our user repository. So let's go to the user repository, and after the AddUserToRoleAsync, let's add public async Task of IEnumerable of string. And it is called GetRolesforUserAsync, and we want a CmsUser object. So here we will return await, we will use the _manager again, and we have a method called GetRolesAsync, and we supply the user id. Now ideally, there should only be one role per user, but there might be people like myself who would actually go into the database and manually manipulate the records. And for some reason, a user might end up with more than one role. So we need to take that into account and we can do that inside of the user service. So we can go back to the user service and we need to get the role that the user has, so we can say, viewModel.SelectedRole =, and then we can check to see how many items are in our user roles. So we can do userRoles.Count. If it is greater than one, then we want to use the first or default method on user roles. So userRoles.FirstOrDefault, otherwise we only have one item, or we might not have any items in the userRoles. So we would use the single or default method. So userRoles.SingleOrDefault. But then we need to also load the roles for the drop down list, and we can easily do that with our viewModel.LoadUserRoles, and we want to await_roles.GetAllRolesAsync. Next we need to go to the update user method, because that is where we are going to update the roles for a user. So down here after we update the user, we need to add the user to the selected role. So, await_users and then AddUserToRoleAsync. We pass in the user and then the selected role. But we need to do more here, because remember that one user should have one role. And if we simply just add a user to a role, then we are possibly adding a another role for a user. So we need to first of all retrieve all of the roles that a user's a part of and then remove that user from those roles, and then we can add the user to whatever is given for the selected role. So the first we need are the roles that a user is a part of, so roles and we can use the newly created GetRolesForUserAsync, and we pass in our user object, but then we want to remove these roles from the user. So we can do await, and then _users, and then we can create a method called RemoveUserFromRoleAsync. We need to pass in the user, as well as the roles, so we can do roles.toArray. And then, of course, we need to create this method, but first of all we need to add this method to the I user repository interface. Let's go to the interface. And let's change this so that instead of accepting an array of strings, this can be params string[] and then roleNames, that way we can pass in each string as an individual argument if we wanted to do that, or we can also pass in an array. So then we just need to implement this in our user repository. So let's go back to the user repository, and we will have public async task, and then RemoveUserFromRoleAsync. We have the CMS user object, as well as the parameters, so that is string, and then role names. And then here we use the user manager once again. So await_manager, there is a method called RemoveFromRolesAsync and we simply supply the user id, user.Id, and then rolesNames. And that should be all of the changes that we need to make as far as the code is concerned. Now we need to update our views, so let's go to our views folder. We need to update the create view as well as the edit view, and we need to add a drop down list. And really, we can just take one of these fields, copy it, and make a few modifications. So, let's do that for this confirm password field. Let's copy that and paste it. For the labelFor method, we want to specify the selected role property, and then instead of using the passwordFor field, we want DropDownListFor. The first argument is going to be the selected role. That is specifying that selected role is the property that we use for, of course, the selected role. And then we need the data store, which is models.roles. And then we need to change the validation message for as well, we need to change that from confirm password to selected role. And then we can basically just copy this and paste it into our edit view. So after the confirm password field, we can just paste that in and we should be good to go. So let's run this and let's first all modify the role for the admin user. But we do need to specify the user in the URL. So that is /admin and here we have administrator the current password we don't need to worry about. But the role, the default is admin, cuz that is the first item in the list, so we can just save and there we go. We can edit the editor and we need to modify these links. So let's take a break here. Let's go to the index view, and let's modify that link, so that it also includes the username. So here, instead of id=item.PrimaryKey, this is going to be username=item.UserName. And we will do the same thing for the delete, because that is the piece of information that we need in order to delete a user. We don't have that ability yet, because we're going to do that through AJAX, but we will get to that in a moment. So let's go back to the browser, refresh, and let's edit the editor. So we will click on Edit, we will change the role to editor and save. Now if we go back to the administrator, we're going to see admin is selected. If we go to editor, we are going to see that editor is selected. Now let's create another user. Offscreen I had created an author, but let's create another user. Let's call him Foo and he will be foo@cms.com. Display name will be Foo, password will be password. And let's assign him the role of author. So if we create this user, it did create them, and if we go to the edit we see that the role is author. So, the creation and the editing are working for our users. Not only are we able to create users, but we can also assign them roles. Well in the next lesson, we need to take this a step further and we need to start securing the admin potion of our CMS. For example, we need to ensure that only admins can create, edit, and delete users, but at the same time, we need to allow a user to update their own information. So that's the type of stuff that we need to do and we will do that in the next lesson.