FREELessons: 22Length: 5.5 hours

Next lesson playing in 5 seconds

Cancel
  • Overview
  • Transcript

6.2 Creating Users

In this lesson, we'll write a user service that is responsible for creating, editing, and deleting users (ideally, we would have a service for each of our controllers). We'll also see that my decision to do everything synchronously comes back to bite us, and we'll have to adjust our code.

6.2 Creating Users

We need to do two things in this lesson. We need to create a user by collecting data from a form. And then we need to assign users to a role. So in order to save some time, I wrote some code in preparation for this lesson. And the first piece of code is something that we should have written earlier, and that is a UserRepository. Because we shouldn't be using identity code inside of our controllers. In fact, we shouldn't be using repositories inside of our controllers. We should have a service layer that is a buffer between our controllers and our repositories. And that's something that we might do whenever we go and clean up our code and stuff. So let's just briefly go over this UserRepository. There's really nothing out of the ordinary here. We have a method to retrieve a user by the name. We can retrieve all of the users, we can create a user. And this signature closely resembles the create signature on our user manager, where you supply the user as well as the password. We can delete a user, we can update a user. And then we implement the IDisposable interface. So anytime that we create a UserRepository, we need to dispose of it, because we have resources here that need to be disposed. But this isn't the only thing that I wrote, I also wrote a RoleRepository. Now we haven't talked about roles, but the API is very similar to users. We have a RoleStore, then we have a RoleManager. And for the sake of simplicity, I did not create custom classes so that we would have CMS RoleStore and CMS RoleManager. Because really, this is the only place that we are going to be using the RoleStore and the RoleManager. So we have the ability to retrieve a role by name. We can also retrieve all of the roles. We can create a role, and then that's it. We don't have the ability to edit or delete rolls, because our system is going to have three roles. It's going to have an admin, an editor, and an author role. And once those are created, we should leave them alone. So let's go ahead and set up the roles for our application. Let's go to the App _Start folder. And let's open up the AuthDbConfig file. Now, this is where we seeded our database with the adminUser. And we could go ahead and change this code to use our UserRepository. In fact, let's go ahead and do that, just so that we can get a feel for what the repository is going to be. So we need a using statement, because we need to dispose of this repository. Let's call this users, and we want to new up a UserRepository. And then, instead of using userStore, we use users. And then we use our GetUserByName method, and this will return our user or it will return null. So if it is null, we create our user. And then we need to add that user to our data store, so we use the users object. We call the Create method, passing in adminUser and then the password. So not a whole lot changed here, but let's also write the code for our roles. And it's basically going to be the same thing. We're going to check to see if we have a role. And if we don't, then we are going to create it. So let's call this roles, and we want to new up our RoleRepository. And the first thing we want to check is for an admin role. So if roles, and then we can GetRoleByName, and admin. If this is equal to null, then we want to create a role called admin. So we can do roles.Create, and then we will pass in a new IdentityRole object where the name is admin. And we basically want to do this two more times, one for the editor role and then one for the author role. So we can paste that two times, and then change the names of the roles. So we have editor and then we have author. And this is going to seed our database with these three roles. So now with that done, let's go to our user controller, and let's start writing the code for creating a user. So let's go to Controllers > UserController. And we need two action methods for Create. So let's add a public ActionResult Create. And this is not going to have any parameters, because we are creating a new user. Let's go ahead and let's add the Route attribute. This is create, and this is also for a Get request, so HttpGet. And we simply want to return the View. And we don't have a create View, so we will need to make that here in a little bit. But let's copy this code and let's paste it. This is going to be for Post requests, so let's change this attribute to HttpPost. And we also need to ValidateAntiForgeryToken. So let's add that attribute as well. This method is going to accept a UserViewModel object. And then we need to do the validation and all of that stuff. Now we could do what we did with the edit method. But, yeah, that is rather ugly and I don't like having ugly controllers. I like having nice and thin controllers. So let's go ahead and let's write a user service, one that's going to first of all, validate the model. Because that's what the user service should be doing. And then it will also use the repository to save the user whenever the validation passes. So we need a few things. First of all, we need a service, so let's go to our Solution Explorer. And let's right-click on the Admin folder, and let's add a new folder called Services. Now this is going to be inside of the admin area, because we really shouldn't need this service outside of the admin. If it turns out we need to, then we can always change that. So inside of this Services folder, let's add a new class and let's call it UserService. Now, because the UserService is going to be responsible for validating the model and all of that stuff, the UserService needs our model state object. Or it needs something like it. Ideally, we would have a wrapper around the model state, that we would pass to this UserService, so that it is not dependent on the actual ModelStateDictionary class. But let's keep some things simple here. So we're just going to pass the model state here. So let's have a constructor, UserService. And let's pass in a ModelStateDictionary. That is in the System.Web.Mvc namespace, we'll just call that modelState. And we also need a UserRepository, so we can add that. We can call that, well first of all, we need the using statement for that class. And we can call it userRepository. And we will also need a RoleRepository, so let's add that. And let's call that roleRepository. And so since we need these as private fields, we can go ahead and add those. So private and readonly, we don't want these to be able to be changed after we have created them. We definitely need our UserRepository, so let's do that first. Let's call that _users, and we will follow the same pattern for our roles. So that will be a RoleRepository _roles. And actually, we should be using the interface for these repositories, I did create those. So let's make that change, so that we can pass any UserRepository and any RoleRepository. And let's go ahead and do, private readonly. Model state dictionary and we will call this _modelState. And so inside of this constructor we will simply assign our private fields, their respective objects. So _modelState equals model state, our _users = userRepository and _roles = roleRepository. So let's go back to our user controller and let's add a constructor here. It's going to be a default constructor and we are going to create one of these services. Let's first of all add a few private fields. Let's go back to the user service. Let's grab the fields for the user and role repositories, so let's copy that and let's paste that. And the reason why I'm doing this is because we need to dispose of this whenever we're done. And the Controller class implements i disposable. So whenever the controller is disposed, we can also dispose of these repositories. And then we can do private readonly UserService and we need a using statement for our services name space. And let's actually call this one _users. We'll call the repository _userRepository, and we'll do the same thing for roles. The reason why I'm doing it this way is because we're going to be using the service inside of the constructor. So I would rather type _users as opposed to _userService. We are only going to use the repositories whenever we create the service, and then whenever we dispose of our repositories in the dispose method. So inside of the user constructor, we want to assign _users a new userService. We want to pass in our model state, and we also want to pass in a new user repository, although we can't do that we need those objects first. So, _userRepository is going to be equal to a new UserRepository, and we will do the same thing for _roleRepository except we will new up our roleRepository. And then we will pass those objects to this user service constructor, so _userRepository and _roleRepository. And let's go ahead and let's override the dispose method on our controller just so we don't forget to do that later. So I liked to put the dispose method at the very bottom Just simply because it's just one of those methods that we really aren't going to be using. So this is going to be overridable. So public override and dispose and let's add a private field. Let's do private bool _isDisposed and then inside of this method, we will check to see if not isDisposed, then we want to dispose of our objects, so we will call _roleRepository.Dispose, and then we will do the same thing for the userRepository. And then we will set _isDisposed equal to true. And then we will call the base dispose method. So lets go back to creates method and lets write the code that we want to write inside of our creates method. So we have this model, so we can do if underscore users and then we can call create. We can pass in the model and if the model is validated and the user is created, then it will return true. And we will simply return, redirect to action, and we will pass an index. Otherwise, we have an issue, so we will return the view passing in the model and the service will be responsible for adding any error messages to our model state. So, I like this, this is nice and clean code. So, we can create this create method on our user service. So let's go back, and let's do public bool create. We are expecting a userViewmodel. We need a using statement for that. Let's call the parameter model, and then we basically want to go through all of our validations. So first of all, let's check to see if the model state is valid, so _modelState.IsValid and if it's not then at this point, we simply return false because the model state already has the errors that we need. And now we need to check to see if our password has a value. So, if string is null or whitespace, and a model.NewPassword then we want to add a model error. So we will use our modelState, and AddModelError. This is going to have an Empty string as the key. And for the error message, you must type a password. That's a little bit better and then we will return false. Now we don't have to do any comparison between the new password and the confirm password because we have that validation already done for us. All we have to do is to check to see if we have a value for new password, and if we do then we can go on. And so now we basically just want to create the user. So var newUser = new CmsUser. We need a using statement for our models namespace. We'll set the display name equal to whatever was supplied by our model. We'll set the email the same way, model.Email. We also need to set the username = model.UserName. And that's it, as far as a new user. So then we will use our repository, _users.Create. We'll pass in the newUser and the supplied password, which is our model.NewPassword, and then we will simply return true. And so now, we just need a form for that, so let's go back to our user controller. Let's right click on any one of the create methods, and let's add a view. The name is create, let's specify a template of create, and the model class is going to be our user view model. Now, we are going to have to make some modifications, like we don't need the current password there. But I think that's really the only thing that we need to change. So we can take the rest of the defaults, let's click on Add. We have our username field, we have the email, we have the display name, we can get rid of the current password. Then we have the new password, the confirmed password and then hit the button for submitting the form. So now let's see if we can create a user. So let's run the application and we are going to automatically be taken to admin/user/create, because we have the creative view open whenever we ran this. And let's create a user called Editor and the email address will be editor@cms.com. The Display Name will be The Editor and the Password, which is in plain text, is password. We will need to change that to the password field. So, let's click on create, and hopefully are going to work although things should have worked by now. So, that means our code is, at least executing somewhat, because we have gotten thus far. But something is evidently going wrong. Let's first of all go to our view and let's change this from using the editor for method, to using the password for method and we need to do the same thing for the confirmation password. And then let's set a couple of break points. Let's go to the user repository and line 31, whenever we call the create async method. And let's also set another right point inside of our user service. We want to set up a break point on line 48 because we want to call the create method on our repository. We want to ensure that that executes, and that we make it to line 48. So let's go back to the browser. Let's click on Create, and we have hit this breakpoint, so that's good. And if I click on Continue, then we should hopefully hit the other breakpoint in our user service. But we don't. We get back to the browser and the little throbber is still going around and around. So we have a problem. And I'm thinking that it is because we are doing something synchronously which should be done asynchronously. So let this be a lesson. If you're going to do something, do it correctly the first time. I knew that we needed to be doing this asynchronously, but I didn't because I am lazy. So, in order to be sure about that, we need to refactor the code. But I'm pretty sure, in fact, I know that, that's what's happening, we are creating a deadlock. Because there are some dependencies that Create Async depends upon that are asynchronous as well. So let's go through the process of refactoring the create portion of creating a user. We're going to make this asynchronous. And then we're going to make all of the other changes off screen, because there's a lot of things that we will need to change. So the first thing we need to start with is our user repository, and then we will just work up the chain there. So with this Creates method, we first of all need the async keyword. We don't want to async void. We always want to async task of some type. It can be just a normal task, or it can be a task of something, which would be a return type. Since we're not returning something here, we are just going to async task. And we also need to change the name of this method to CreateAsync because that is the convention. If a method is asynchronous, then it has async at the end. So now all we need to do is await_manager.CreateAsync, but we no longer need the .Result. So that should get that code working fine. Now we need to go to the user service because this is the next object in our chain of objects. So basically we need to make this asynchronous as well, so we will async, and this will be a task of bool and we need a using statement for the tasks name space and then we want to await this. But we have an issue here, let's see what the problem is. Oh, it's our iuser repository. We need to change the interface. So, let's copy this part of the method signature and let's go to the IUserRepository interface and we just need to change this from Void Create to Task CreateAsync. Let's add a using statement for the task's namespace, and that should be fine there. So if we go back to the user service, then we just need to call the CreateAsync method, and that should work there. So now we need to go to the controller because that is the next object. The user service uses the user repository, and the user controller uses the user service. And here we need to make this Create method asynchronous. So public async and this will be a Task (ActionResult) and we basically want to use the await keyword in front of _users.Create, and that should be fine there. So, we should be able to run this and, no, we can't because we have something else. And this is inside of the off db config, because we use the repository here to create a user. So that's easy enough to fix, we just need to make this asynchronous as well. So we will use the async keyword, and instead of void, it will return task and then we just want to await users.CreateAsync. And that should be it. So let's run this again, and then we will try to create that editor user. So we need to go to admin/user/create, and the UserName will be editor. The email address will be editor@cms.com. The display name will be The Editor. And then the password, which is now with a password field, is password. So let's click on Create and nothing. Oh, we've hit a breakpoint. So let's clear the breakpoint, let's continue on, and voila, we have created a user. Now ideally, everything else should be asynchronous. So this could be something that we could do off-screen. For this lesson, I'm going to supply two sets of code. One is going to have the code as it is right now, and then there will be code that I will do off-screen. It will essentially make the user repository completely asynchronous, it will do the same thing for the role repository and then the user service. But I'm also going to take it a step farther and I'm going to refactor the user controller because I'm not happy with how all of the validation code is inside of these controllers. So I'm going to put all of this logic inside of the user service so that most of the methods are going to be as simple as the create method, where we just call a method on the service. And if that succeeded, then we do something like redirecting to index. Otherwise, we're going back to the view. And since I've given you this example, you can use this as an example for all of the other methods. And then at the beginning of the next lesson, we can go over the code that I've written and you can compare it with yours. So that means we are going to have to put off the whole role management until the next lesson as well, but that's okay. So go ahead and make those modifications, make everything asynchronous as least as far as the users are concerned. At the beginning of the next lesson, we will go over my solution and then we will start working on managing roles for a user.

Back to the top