- Overview
- Transcript
9.1 Displaying the Published Posts
We haven't had a need to display only the published posts. We'll write that functionality in this lesson.
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
9.1 Displaying the Published Posts
For the most part we're done with the back end of our application, now we just need to focus on the front end. And by that I don't mean JavaScript or anything like that, it's simply what visitors are going to see whenever they come to our website. So if they come to the index, or to the root, of our application they're going to see a list of published posts. Now we haven't done anything with published posts. So we need to add the ability to retrieve those from our data store. So we're going to add some methods to our I plus repository interface as well as the plus repository class. So let's start with the interface and let's define a method that's going to return a task of IEnumerable of post and let's call it GetPublishedPosts. And let's copy that signature, and let's go to the post-repository and implement that method. So I'm going to paste that in and then put public async in front of it, and we basically need our context. So a using statement var db equals new CmsContext, and then we want to return await, and then db.Posts. Now in order to determine if a post is published, it has a date. So we want to retrieve all of the posts where the published property is less than now, so DateTime.Now, and then we can call the ToArrayAsync method to give us those results. So let's put this on multiple lines, just so that we can read that a little bit better. And now that I think about it, we need an OrderBy clause here, so let's OrderByDescending and we also want to order by the publish date, because we want them in reverse order so that the latest published post is going to be at the beginning of this collection. So we want to order by the published property. So now we need a controller, and we don't have one yet. So let's go to the controllers folder, and let's go ahead and let's just add a home controller. That's the convention for the home of our application. So let's add a new controller called HomeController, and we get an error. It says that a file or folder with the name Home already exists on disk at this location. That is interesting because we haven't created a home controller yet. So if we right click on our controller's folder, let's go down to Open Folder in File Explorer, and, of course, there is not a file here called HomeController.cs. So let's look at the views, let's see what we have. There's a home here that as you can see it's not really there, so let's not show all of the files. And let's add a new controller and let's see if that fixes this particular problem. If not and it doesn't then let's try to create another controller and I shouldn't have closed that and let's just call it default controller, and then let's try to rename it. So this is an interesting issue, I'm not sure if you are going to have this on your computer, the only way to try it is to try it. So we created a default controller, and renaming it to home controller worked. That's pretty interesting. Okay, so inside of this home controller we are going to be working with a post repository. So let's go ahead and let's add a private read only IPostRepository. And we need a using statement for our data namespace. Let's just call this _posts. And we need a constructor that is going to accept an IPostRepository object as its argument. So let's call this postRepository. And then inside of the constructor we will set our _posts = PostRepository, now we also need a default constructor, so once again HomeController and we'll do the same thing that we did with all of our other controllers. Where we pass in a new PostRepository object. So now that we have a postRepository that we can work with inside of this controller, inside of the index method we can retrieve the posts that are published, so _posts and then GetPublishedPosts and we should have added the Async word at the end of that, to denotes that it is synchronize. So let's quickly make that change and inside of the controller we need this to be a public async task of ActionResult and then we will pass our published posts to the view. Now let's talk about our URL's, because they are very important. Now that we are talking about the front end of our application, they need to be very easy to use. So the Index action method is, of course, the root of our application. So as far as the URL is concerned, it will be root, and nothing else. But let's say that we want to have a URL for displaying an individual post. We can have root and then posts and then we can have post-id, that's rather logical. We can do the same thing for tags. So, in order to display the posts with a given tag we can have root/tags and then the tag-id. So in order to facilitate these urls we need to add some route attributes. First of all, at the HomeController level, we need the route prefix to be an empty string because if it's not then we're going to have home/posts/post-id, and we don't want that. So an empty string is going to give us what we want. And then for the index, we need a route attribute here with an empty string. And let's go ahead and let's write a method, for displaying an individual post. So the route in this case is going to be posts, and then postId. And we need to wrap that around curly braces. And the method itself will be public async task actionresult and let's just call it to post and it will accept the postId. And let's copy this and paste it for the tags as well and then we will of course need to modify it so that the route is tags, and then tagId. The name will be called Tag, that will keep it consistent with what we have with post, and then the parameter is tagId. So let's start with the post method, we want to retrieve a post with the given postId. So if our post = _posts and we have the Get method. Now, ideally we would turn this into an asynchronous method. But, we are also using the get method in other parts of the application, and that would force us to have to refactor our code in those cases. So, for the sake of simplicity, we're just going to keep this as is, and do this synchronously. So, we will try to retrieve the post with the given ID, let's check to see if post exists, so if it's null then we want to return a HttpNotFound. Otherwise we want to return the view passing in the post. Now for the Tag method we don't have the ability to retrieve posts with a given Tag, but we can easily add that. But let's first of all write the code that we want inside of this method first, and then we will implement it later. So of our posts equals and then we will use our post repository but we need to await here _posts and then GetPostsByTag, and then we will put in the tagId. Now it is possible that we don't have any posts with a given tag, in which case the tagged doesn't exist. So if not posts .Any(), then we want to return HttpNotFound(). Otherwise, we will return the View(), passing in our posts. So let's add Async to the end of this method, and then let's add it to our interface with Ctrl, dot, and Enter. And whenever we got to the interface we just need to change this to a task of IEnumberable and then we will implement this inside of our repository. So we need public async and then we want to create a context object, so using var db = new CmsContext(), and here we want to retrieve the posts with the given tag. Now, we have kind of already done that inside of the TagRepository, so we just need to find that code and we can use it as a basis for the code within our PostRepository. So right here with the get method, that is what we want, so let's paste that in and then we will make our modifications. Going to put this on multiple lines to make it easier to read. Let's change tag to tagId and we want to use await here. And then instead of calling to list, we will call to ListAsync and that will give us asynchronous operations. And this will, first of all, retrieve the posts with the combined tags that contain the tagId. But then we need to whittle that down even more because there's no guarantee that that's the actual id that we getting. So here we will use the where clause and then we will use our Tags property, that is our IE numerable of sting, and we will get the posts with the given tagId. And that's basically all we need to do here, so we can simply return this statement, and that will give us what we need. Well, let's move on to our view, and we'll start with a partial view for rendering an individual post. Because regardless of what view we are going to have, if it's going to display a post, it needs to use this little partial view. So let's add a new folder to our Views folder, and let's call it Home, and then inside of this Home folder we will create a new View, and let's call it _renderPost. And we can take all of the defaults but make sure that Create a partial view is checked. Now for the model, this is a post, so we'll need our Post class and that is inside our model's namespace. And then we just need the markup for displaying a post. Let's use an h2 element for the title, so @Model.Title. And then we can use a paragraph for the content, so @Model.Content. Now our content is not going to contain HTML, at least it won't inside of the database because as we are creating a post or editing a post, if we try to input HTML, we are going to run into an exception, because by default ASP.NET is not going to allow you to do that. An alternative would be using something like markdown, so that's inside of the database. All of the content is using markdown and then whenever we retrieve it, so like inside of the Home controller, we can retrieve the posts and then we could convert that markdown into HTML and then we can display that HTML. So I guess we need the @Html and then call the Raw method here, passing in the contents, because if there is HTML we definitely want to display that as HTML as opposed to the HTML entities on the screen. Let's add another paragraph and let's do By: and then we want the author's name so Author and DisplayName. Now we need to include the Author within our query for retrieving our posts. So we will need to do that inside our PostRepository. And then we need our tags. So we can do something like Tags: and then we could have another partial view that is responsible for rendering our tags. So we could do Html.Partial and let's call this _renderTags. And then for the model for this partial view, we want our Model.tags. So let's go ahead and let's create this _renderTags partial view. So let's go back to our Home folder, let's add a new View, and its name is _renderTags. We'll take all of the defaults, and now we need to specify the model, which is an IEnumerable. Now we could use a foreach loop to iterate over our collection of strings but we also need to think visually. If we are going to display a list of tags, we need to separate them with something. It doesn't matter what it is as long as we are separating them. So it could be a comma or comma space, or a pipe, or even just a simple space. Now, we could do that with a foreach loop, but we also have to add some extra logic. We need to determine what is the first and the last element within the collection, and then we need to decide where we put the separating character or characters. So instead, let's use the string class's Join static method, because that is basically what we get and we get it out of the box, and we don't have to write any extra code. The only thing we do have to do is transform the strings inside of our collection into a link. So we can use a link to do that. So first of all we need our separating character or characters and let's just do comma, space. And then we need our collection. So we can use our model and then we want to select each string within this collection and we want to call html.ActionLink(). The first argument is going to be the string because that is the text that we want to display. And then we need the action, which is tag. And then the route information and that is a new object, and we will set controller = "home". And I believe that is all we need to do. We can set this on multiple lines just to make it a little bit easier to read. And we have an error here, it's probably missing another parenthesis, and it is. So this should work. And let's go to our Home controller. Let's go to the Index view or action rather. And let's create the Index view. So let's add a new view. This is not a partial view and it's going to use _layout, and I don't think we have that yet. So let's go ahead and let's create this, and then we will check to see if we have _layout. In fact, I know we don't. So let's take the admin layout from our Admin area, and let's use that as a basis. So let's find the Shared folder. There it is. We can just copy this file and we can create a Shared folder inside of the Views at the root of our project and then we will paste that file in here. Let's change the name to _layouts, instead of _admin layout. And for the most part, we want everything here. We want the CSS, we also want the JavaScript. We don't really need the admin here, so we can get rid of that. And we don't need the AdminMenu. We do, however, need this AuthenticationLink because we do need to be able to login, if that is something that we need to do. So we can use this as a basis and we might come back and add a few things but for now let's just close that file. And the model for our Index view Is an IEnumerable of post. So @model Enumberable Of post. And let's see if we can do that same thing that we did inside of rendered tags, by using string.Join because we want to separate these posts with something, like a horizontal line, or something like that. So we can have Model.Select, let's change the s to p. We want to call HTML Partial, and that is _renderPost, and then the model is the post object itself. And for the separator, we don't want a comma space, instead, we want an hr element. So I'm interested to see if this is going to work. So, let's run this, and let's see if it does indeed work. Now, home/index probably won't, we can see that we get a 404. So let's get rid of home/index, let's just go to the root of our application. And we have a problem, and it's because we don't have the author. That is something that I forgot to do. So let's go to our post repository, and we need to add in the include for the author inside of the GetPublishedPostsAsync method. So before our where let's go ahead and include, and we want to include our author, and that should be sufficient. So let's run this again, it will take us straight to the root, and hopefully everything is going to work. Well, in a way it worked. [LAUGH] It didn't work exactly how we wanted it. The razor view engine is encoding our HTML, so that is something that we need to address. So let's go to the index view, and let's see if this is going to work. Let's try to do Html.Raw. I'm really not even sure what the Raw method returns. I haven't looked to see what it does. So let's see. Well it obviously doesn't like this so we can't use Html.Raw here. So unfortunately that's not going to work. So let's just do this. Add for each of our post in model, and we are going to call Html.Partial and then a pass in post as the model. We need the at sign here. And we can add an HR after each post. So that should work with that. Let's go back to the browser and refresh. And there we go. Although, no, no, no no. I'm not going to accept that. Let's back out all of this. And I should have just thought of this before. Instead of calling Html.Raw here, we just need to pass this to Html.Raw and then string.join. And then everything should work there, so let's go back, let's refresh, and hopefully. Yeah, that kinda worked. We are still missing our tags, but we can go to our render posts, or rather the render tags, and we can do the same thing here. We can do @Html.Raw and then pass in string.joy. So, now we should have what we want. And we do. We have testing here but the URL is not correct, it has home/tag and all of our other tags are the same thing. So, there's two things we need to fix. First of all, we need the tag id, that is something that I forgot to add here. So we need tag id, I believe it is camel cased, but let's look at the controller just to make sure. And, yes it is. So, tag id is going to be equal to whatever we have specified as s. And that might fix the URL issue. So let's just refresh and let's see what we have. And yes that did indeed did work. Now we have just /tags and then testing in this case, and then another tag, and so on and so forth. Now we need a link for our H2 elements, but we don't necessarily want to have a link every time we display a post. Because if we are reviewing just an individual post, then there's really no need to have a link there. So we can do something like this. Let's go back to the index view, and we're going to use our view bag, we're going to say ViewBag and then IsIndex, and we're going to set this to true. And then we can go to the render post partial view, and we can add a check here. So if and then ViewBag.IsIndex. Then we want to display the title as a link. Otherwise, we are going to display just the title. So let's move that little bit of code inside of the else, and then we need to use the HTML action link method. Now the text for this link is going to be the title, so we can use model.title. The action is post, and then the route information. The controller is home, and then we need to specify the post ID, and this is going to be our model.ID. And that should be everything that we need here. So if we go back to the browser we should have a link for each one of these titles, and we do. And looking down at the bottom, it looks like the URLs are being generated correctly. So we could click on this, and we should get an exception that we don't have a view. So the link is, at least, working. And if we click on one of the tags, we should get the same results because we don't have a view for post or tag. So let's go ahead and let's create those. Let's go to the post action method, let's right click and add a view. We'll take the defaults, and we need to specify the model as our post. So MvcCms.Models.Post and then we basically just want to render our partial view. So Html.Partial, we will pass in _renderPost, and then we will pass in the model as the model. Now let's create the tags view, and it's going to be a lot like the index because we have an IE numerable of posts. So let's add, we'll take all of the defaults. But inside of the repository, we should also add in the author. So inside of the, GetPostsByTagAsync, let's add in the include, and we want to include the author. And to save some time, let's go to the index view and let's just copy the relevant pieces of code. First of all is the model declaration, and then we have the code for displaying the individual posts, so we will just add that. And let's do posts, tagged with, and then we can have our tag and we can put this as the ViewBag as well. We can have @ViewBag and then we can just have Tag. So inside of the Tag Action Method we need to set that ViewBag.Tag = to the tag ID. And so with those additions we should be able to run this application. We will of course see our list of posts with the index. If we click on one of the titles of the posts, it should take us to the individual post page. Although we get an error. And the problem is with this ViewBag property. It's probably because we don't have the IsIndex set inside of the post view. So let's continue, and then we can go to the post view and we could add that same thing. So we could say ViewBag.IsIndex, and we want to set that equal to false because that is not the case now. So we can go back to the browser. Let's refresh, and we should see the individual post. We do. If we click on the tag, that should take us to the same type of thing. And once again, we need to specify this is index, but we need to do that inside of the tag view. So we can go here, and in this case, it is an index. So we can say viewbag.IsIndex = true. Because if we are going to display the posts by their tags, then we also need the link specified for the title. So let's continue. That's going to cause the error to show in the browser. Let's refresh, and now we should see our posts with the given tag. Well, there's one other thing that we need to do to our front end, and that is to add paging it to our index. Because it's not good enough to just list all of our posts all at one time, we need to break those up into individual pages. And we will do that in the next lesson.