4.2 Providing Different Views
We should have more ways of displaying data than the simple list of messages we're showing now. We need a way to display messages by user as well as to display single messages. We'll use attribute routing to achieve this, and in this lesson I'll show you how.
1.Introduction1 lesson, 01:12
2.Getting Started2 lessons, 22:54
3.Request Handling5 lessons, 1:06:56
4.Displaying Data5 lessons, 1:15:23
5.Conclusion1 lesson, 00:39
4.2 Providing Different Views
Now that we are displaying a list of messages, it would be nice to filter that list down. For example, so that we could display just the messages written by a specific user or for just displaying a single message. Let's first of all talk about the URLs that we would use to access these different views, which is essentially what they are. They are different ways of viewing the same type of data. So these are the URLs that we have currently. We have message and then create, then we have message and then edit and 1 or whatever the ID is. So we can think of message kind of as a container, and with that in mind, it makes sense to use message and then we could have something like the user name. Which would be the email address for our system. So if we wanted to display all the messages written by email@example.com, then that can be our URL. And that makes sense, and the same thing can apply to displaying an individual message. So that we would have message and then we could just have the ID. So if we wanted to display the message with an ID of 1, then this could be our URL. Now, this is going to pose a problem, because as we are currently routing our requests, they're going to controller, action, and then, if the idea is specified, then that value is there. Well, these URLs do not follow that pattern. Instead, we have controller and then some other value that we need to pass to an action method. So this is going to require us to modify our routing table. Now there's also something that I want to touch on, and that is just consistency. Like we have message, and then edit, and then 1, and then down here we have message and then 1. Wouldn't it make more sense to change our edit URL so that it's message and then the ID and then /edit? I think that that's ideal, and it of course is going to cause us to rethink how we are routing the requests to our controller. Now a couple of lessons ago, we briefly looked at the routing table that's inside of start up CS. And if we scroll, I think it was all the way down, yes it is. We have the use MVC. And we are setting up the routing table. We can add our own entries, but it's going to add a lot of complexity, a lot of confusion, it's just going to make things very unruly very quickly. Because we are going to have to define several routes. We will have a route for this URL, we'll have another one for this URL, and probably another one for this URL. So, we are looking at at least two more routes in our routing table, and that's just for our message controller. And we also have to be concerned with how we define them. We have to define our routes from the most specific to the most generic. So, we could do it this way. Or we can do it directly inside of the controller by using attributes. I personally like this approach because it keeps everything here in the controller. And there's not a lot of ambiguity by using attributes routing because everything is here. It's described within attributes. Whereas if we add an entry into the routing table, well, things can get a little confusing. So we can define our routes using three attributes. The first is called simply route and we can use this attribute at any level. We can use it at the controller level. Or, if we wanted to, we could do it at the action method level. So, let's look at what we could do for the create action method. We could add this route attribute and then we could say that message/create should be routed to this action method, and that would work. But we don't really have to specify message here because we can go to the controller and we can say that, okay, everything inside of this controller is going to begin with message. That's just default. So we can say Route and then message up here at the top. And now everything that we want to access within this controller starts with message. So we can come back down to this creates method and we can get rid of message there in the route. So, Route is the first attribute. The second attribute is httpGet. Instead of using the route attribute here for the create action method, we can just say, okay. We want to combine essentially httpGet and the route. So this is going to be for create. And there we go. We have now added a routing entry, if you will, into our routing table so that if we get to message/create. Then we're going straight to this creates method. Now we could also use a special token that begins with square brackets. We could say that this is action. This is going to take the name of our action method create and substitute it here. So if we ever decide to change the name of our create action method to say, add, then we don't have to change our route. That's automatically going to be done. Add is going to be the new URL instead of Create, but we'll leave this Create. And then we want to do the same thing down here for the other create method. But we can use the HTTP post. So that is the third attribute that we can use. So HttpPost and we want to use that special action, so we will specify that and then we are good to go as far as the routes for our create action methods. So now we just want to finish the rest of what we have. Now our edit action method is going to be a little different. If you remember that we are going to switch it so that it's message, and then id, and then edit. So this is really easy to do here. All we need is to add in our route value. So we can say that okay, this route is going to be ID first, so you would use that parameter, that is essentially used down here, if you remember, but in this case, this isn't optional. This is required, so we want the ID there, and then we can say, either edit, or we can use that action token once again. And then we essentially want to do the same thing for the post request version. So let's go down to the edit method for HttpPost. We'll just add that route. And we're good to go there. And let's take a few moments and talk about the differences between the curly brace tokens and the square brace tokens. The curly brace tokens are values that are going to be replaced from things within the URL. So the routing engine is going to take the first segment after message and it's going to plug that value in here and that is what the curly brace token is for. The square brace token is for replacing from our code. So the routing engine is going to take edit in this case, and replace the square brace token with that edit value. So curly braces come in from the request, the square brackets come in from our code. Now there's also a controller token, so if we go to the controller level, and instead of message, we could have controller inside of these square brackets. And this is going to take the name of the controller, of course minus the controller part of the name, so it would be simply Message and plug that in for controller, but I don't want to do that here. In fact, I want to change this. I want this to be messages, because to me that makes more sense. This is the beginning of working with our messages. So if we go to the index, then we are seeing all of our messages. If we go to Create, we are creating a new message for our messages. If we are editing a message, we are editing a single message inside of our messages. So we can use whatever values we want for these routes. Of course we could change the name of the controller to simply messages controller. But that's not easy to say. And plus I like the name Message Controller. So we're just going to leave it as is where the name is the same, but the route is going to begin with messages. Now there's one other method that we need to address and that is the index method. Because whenever you use attributes routing, you have to be specific for every action method that you want to handle a request. So, for our other action methods, we're done. But we need to do something for the index action method, because we no longer can rely upon our default's routes. The default action is set to index. Well, we lose that whenever we use attribute routing, but this is easy enough to fix. We just add a route attribute, pass in an empty string and we are good to go. And so for our existing code everything is going to work, but we should also add some action methods for handling our new circumstances. For displaying the messages for a given user. And then displaying a single message. Now, we don't have to add new action methods. We could reply upon index for some of this, but I personally like to add another action method because while the behavior is going to be similar to index, it's still going to be different enough to me to think that this is behaving differently. And plus, if we ever decide that we need to do something specific inside of the methods handling for the user name or a single message, then we already have the methods there. We don't have to do anything else or add extra complexity to our index action method. So let's start with our allow anonymous attributes because these are things that a non authenticated user should have access to. And we would use our routes and we want a route that has the usernames. So we can use the curly braces and we can say that this is going to contain something called the user name and this is going to interact with our data stores. So we want this to be asynchronous, which means we need to return a task of I action result, and we can call this method anything we want, because from the user's standpoint they are going to go to messages/ and then username. They're not going to see the method that we see. So we can make this as specific as we want to. Let's do a DisplayByUser. And then we will have the username as the parameter and then we can copy the code that we have inside of index just for placement purposes. We actually need another method to retrieve the messages by users. In fact, we could just have another overload of GetAllMessages. So Get AllMessages async and then we could say that this is for a user name. And then we can copy this code and use it as a basis for our other circumstance where we want to display an individual message, in which case we need to change our route. This is no longer for the username. We want this to be by id, but we also want to be specific. Our ids are integer values, so we can add a constraint to this. We can say that this id needs to be an int. And that way the routing engine can determine if we have an int then we're going to route the request to this action method, which we will call display by id. Let's go ahead and change the parameter as well. But if we have a string value, then it's going to assume that it's a username. And it's going to route that request to the display by user. Now we can go ahead and create this get all messages async method that accepts a string. Just put your cursor over that, Ctrl+dot+Enter, and that will add that method to our service. But as you see, there are some other issues that we will need to address. But we will whenever we fully implement that method. Now for the DisplayById, we don't want this method to be called GetAllMessagesAsync. Let's just say GetMessage, well we just could say, GetMessage and then we can pass in the id, and then that will be sufficient enough to be descriptive as to what this method does. So we will do the same thing, just put your cursor over the method, Ctrl+dot+Enter, and it will add that method to our service. Now in order to implement these methods, we will need to have our repository open, because I don't think we have anything as far as retrieving the messages by a given user. So let's go to the data folder. And let's open up the message repository. We also need to open up the view message service. And if you scroll down to the bottom, you'll see our new methods. Now Visual Studio created these methods with the incorrect return type. And they are also missing the async keyword. So we need to change that. For the GetAll messages Async method, we can just copy and paste from the other overload, but for the other method, the GetMessageAsync, we need to add the Async keyword. And the we want to return a task of MessageViewModel and it helps if you type that correctly. So, there we have our definitions, now we just need to implement these methods. And let's start with the GetMessageAsync method, because that's a little more straightforward. The first thing we want to do is retrieve our message. So we will use our messages repository, and we have that GetBy method where we will pass in the id. Now it's possible that we don't have a message with that id, so let's check to see if it's null. If it is, we will simply return null, and then in the controller we will check to see if it's null and return a 404 if that is the case. But if we do have a message, then we want to get the user associated with that message. So we want to do this asynchronously so await_db.users. And then we want to use the single ASIC, because if we have a message then chances are pretty good we have a user. Although, well, in the real world we would want to go with single or default and then make the appropriate choice. If we have a user then populate the user information if not, then we don't. But we're going to take the easy way out and just simply use SingleAsync. And we want to select the user where its id is equal to our message.UserId. And then we want to convert this MessageObject to a MessageViewModel. So we can come up here, we can start with this code. We, of course, need to make some changes because we are not returning a collection. Instead, we are returning a single message view model object. So we can get rid of the link stuff, we could change all of these ms to message. But, let's change message to m. That makes that easier, but then when it comes to the user name, we want to use our user object. So we will have user, and then email, and that will be it for that method. Now the get messages asynch method is going to be about the same thing but in reverse. We want to get the user and then we want to get the messages for that user. So let's start by copying the code that we had in our get message async. But instead of selecting by the ID, we want to select by the email address and check that with our username. Now in this case we definitely want to use single or default, because we might not have a user with the given user name. So let's check to see if we have a user. If user is null, then we can return either null or we can return an empty collection. I guess if we return null, that would make more sense because there's nothing here at all. That way we could know that we need to return a 404 in the controller, so if user is null, then return null. Otherwise, we want to retrieve all of the messages by the given user ID. Now we don't have that method, but let's go ahead and write the code that we would normally write. So let's do var messages equals and we would use _messages and we could create another overload of Getby but let's do GetByUserId(). And then we will pass in the user's ID. That will give us our messages and then we will essentially do the same thing in the other method, where we return all of our messages but we convert them into an array of MessageViewModel. Let's go ahead and hit Ctrl+dot, on get user by ID. That will create that method in our message repository, so let's go to that, and there it is. All we want to do is, well first of all we need to change the return type. It is an IE numerable of message. And we want to select all of our messages where its userID is equal to our IDs. So for our lambda expression we will say m.UserId ==id. And we can go ahead and execute that deferred query with the two array method. Now let's change this parameter to userId. That way, it's a little bit more explicit as to what this is. And we also need a return statement there. So we can go back to our view message service, and the only thing we need to change now is the username. We don't have this user's dictionary, but we do have the user itself. So we can say user.email and that will work there. Well now we are almost finished displaying our messages. We are now showing all of our messages, but we have the plumbing ready to go for displaying messages by user and displaying messages by ID. Now we just need to write the views and we will do that in the next lesson.