1. Introduction
There are different ways we can learn a technology: by reading books, attending lectures, making samples and more. I think one of the best ways is to create something useful for ourselves using technology. One of the useful things that you could easily create is a blog. In this multi-part series, we’ll learn ASP.NET MVC step by step by creating a blog from scratch.
To keep things simple, we’re not going to build the comment system, but we’re going to use Disqus instead. I encourage you to build the comment system yourself and that would be a good exercise for you.
We are going to use ASP.NET MVC 4 to develop the application. I am not good at Entity Framework and we are going to use Fluent NHibernate/NHibernate combination to build data access system. You could use the Entity Framework if you wanted. Finally, we are going to use Ninject for dependency injection due to its simplicity.
In the first part of the series we are going to build the basic infrastructure of the blog. Let’s create the necessary model classes, data access components, controllers, and views. By the end of this part, we have a working blog where we can see the most recent posts, read an entire post, find posts based on a category or tag, and even search for interested posts.
In the second part, we’ll create an admin console to manage our posts, tags, and categories.
In the final part we are going to integrate the Disqus comment system with our blog. We also see about the integration of AddThis, FeedBurner for sharing and subscriptions. Last but not least, we also take necessary measures for SEO.
2. Technologies
3. Part I – Create the basic infrastructure. Create the necessary model classes, data access components, controllers, and views.
Let’s look at the user stories we’re going to complete in this part.
3.1 User Stories
Story #1: Show the latest blog posts
Story #1. Story #2 – Display posts by category
Story #3 – Display posts by tag
Story #4 – Find Posts
Story #5 – Show details of a single post
Story #6 – Show posts post categories in a widget
Story #7 – Displays post tags in a widget
Story #8 – Displays the latest posts in a widget
4. Story #1: Display the latest blog posts
What we’re going to accomplish in this story is read the blog posts from the database and display them in a view.
Before implementing the story, we need to prepare the basic configuration. We have to create the solution and the necessary projects.
Create an empty solution with the name JustBlog.
Create an MVC 4 web application with the same name as the solution, ie JustBlog. In the “Select a template” window, select “Empty template”.
Create a class library and name it JustBlog.Core. It is a good practice to keep the domain classes and data access components in a separate project and that would help us manage the application more easily in terms of development, testing and deployment. Don’t forget to add a reference to JustBlog.Core in JustBlog.
Our basic configuration is ready. The solution will be seen below after adding the necessary projects.
This is a slightly larger user story. We are going to do the initial data access and DI (Dependency Injection) setup work as part of this story.
Let’s break our user story into smaller tasks and that will help us implement it easily.
1. Create domain classes 2. Configure Fluent NHibernate and NHibernate 3. Create mapping classes, data access classes and methods 4. Configure Ninject for the core project 5. Configure Ninject for MVC 6. Create Controller and Actions 7 Create View
4.1 Create Domain Classes
Create a new folder named Objects in the JustBlog.Core project to place the domain classes. We need to create three domain classes for our blog: Post, Category, and Tag. Each post belongs to a single category and can be tagged with many tags. Between Publication and Category the relationship is many to one and between Publication and Label the relationship is many to many.
The relationship between the classes is illustrated in the following diagram.
Here is our Post class.
namespace JustBlog.Core.Objects { public class Post { public virtual int Id { get; place; } public virtual string title { get; place; } public virtual string ShortDescription { get; place; } public virtual string Description { get; place; } public virtual string Meta { get; place; } public virtual string UrlSlug { get; place; } public virtual bool Posted { get; place; } public virtual DateTime PostedOn { get; place; } Public virtual datetime? Modified { get; place; } public virtual Category Category { get; place; } Public virtual IList Tags { get; place; } } }
Listing 1. Post model
Most of the properties are self-explanatory.The UrlSlug property is an alternative to the Title property being used in the address.
For example, if we have a post titled “Advanced Linq in C#” published in August 2010, we’ll create the URLs so that the post can be accessed from the http:// address. localhost /archive/2010/8/Advanced Linq in C#. The post title may contain special characters (in this example there is a “#”) and not all servers can handle those requests that have special characters. Instead of using the Title property directly on the URL, we need to use alt text that resembles the post title and is called URL Slug.
In the above case, instead of using “Advanced Linq in C#” in the URL, we’re going to use a slug like “advanced_linq_in_csharp”, so the address will be http: //localhost /archive/2010/8/advanced_linq_in_csharp. In Part II, we will see how to automatically create a slug from the post title.
The Meta property is used to store the meta description of the post and is used for SEO. One interesting thing to note is that all properties are marked as virtual and it is also very important. NHibernate creates a runtime proxy for this class and for that all properties must be virtual.
The Category and Tag classes are simple, as shown below. We’ve used the UrlSlug property for the same reason we discussed above.
namespace JustBlog.Core.Objects { public class Category { public virtual int Id { get; place; } public virtual string Name { get; place; } public virtual string UrlSlug { get; place; } public virtual string Description { get; place; } public virtual IList Publications { get; place; } } }
Listing 2. Category model
namespace JustBlog.Core.Objects { public class Tag { public virtual int Id { get; place; } public virtual string Name { get; place; } public virtual string UrlSlug { get; place; } public virtual string Description { get; place; } public virtual IList Publications { get; place; } } }
Listing 3. Tag model
4.2 Configuring Fluent NHibernate and NHibernate
We are going to use NHibernate together with Fluent NHibernate for database access. NHibernate is an ORM tool much like the Entity Framework where relationships between classes and tables are mapped via xml files. Fluent NHibernate is an extension to NHibernate that replaces xml files with classes. Mapping via classes is much easier than xml files.
We can easily add references to NHibernate and Fluent NHibernate assemblies via the Nuget Package Manager Console.
Open the Package Manager from Tools -> Library Package Manager -> Package Manager Console.
Run the following command from the console.
Installing the Fluent NHibernate package will install the necessary assemblies. If the installation is successful, we will see the following assemblies added to the References.
FluentNHibernate NHibernate Iesi.Collections
4.3 Creating data access classes and methods
The next thing we need to do is create the necessary mapping classes. A mapping class is used to map a class and its properties to tables and columns. Create a new folder called Mappings in the JustBlog.Core project to hold all the mapping classes.
This is the mapping class for Post.
using FluentNHibernate.Mapping; using JustBlog.Core.Objects; namespace JustBlog.Core.Mappings { public class PostMap: ClassMap { public PostMap() { Id(x => x.Id); Map(x => x.Title) .Length(500) .Not.Nullable(); Map(x => x.Short description) .Length(5000) .Not.Nullable(); Map(x => x.Description) .Length(5000) .Not.Nullable(); Map(x => x.Meta) .Length(1000) .Not.Nullable(); Map(x => x.UrlSlug) .Length(200) .Not.Nullable(); Map(x => x.Posted) .Not.Nullable(); Map(x => x.PostedOn) .Not.Nullable(); Map(x => x.Modified); References(x => x.Category) .Column(“Category”) .Not.Nullable(); HasManyToMany(x => x.Tags) .Table(“PostTagMap”); } } }
Listing 4. PostMap class
To create a mapping class we must inherit it from the Fluent NHibernate generic ClassMap class. All assignments must be done in the constructor.
The id extension method is used to represent the name of the property that should be set as the primary key column of the table. By default, Fluent NHibernate assumes that the table name is the same as the class name and the column name is the same as the property name. If the table name is different, we need to map the table to the class using the table extension method.
Ex.
Table(“tbl_posts”);
Listing 5. Table extension method
The Map extension method is used to map a property to a table column. When mapping a property, we can specify the size of the column, whether or not it is nullable, and other details. If the generated column name has to be different from the property name, we must pass the column name using the column extension method.
Ex.
Map(x => x.Title).Column(“post_title”)
Listing 6.Map Extension Method
The References method is used to represent the many-to-one relationship between Post and Category via a Category foreign key column in the Post table. The HasManyToMany method is used to represent the many-to-many relationship between Post and Tag and this is accomplished through an intermediate table called PostTagMap. You can get more details about Fluent NHibernate and its extension methods from here.
The Category and Tag mapping classes are pretty much the same, except for their relationship to the post. The category has a one-to-many relationship, while the tag has a many-to-many relationship with the post.
namespace JustBlog.Core.Mappings { public class CategoryMap: ClassMap { public CategoryMap() { Id(x => x.Id); Map(x => x.Name) .Length(50) .Not.Nullable(); Map(x => x.UrlSlug) .Length(50) .Not.Nullable(); Map(x => x.Description) .Length(200); HasMany(x => x.Posts) .Inverse() .Cascade.All() .KeyColumn(“Category”); } } }
Listing 7. CategoryMap class
namespace JustBlog.Core.Mappings { public class TagMap: ClassMap { public TagMap() { Id(x => x.Id); Map(x => x.Name) .Length(50) .Not.Nullable(); Map(x => x.UrlSlug) .Length(50) .Not.Nullable(); Map(x => x.Description) .Length(200); HasManyToMany(x => x.Posts) .Cascade.All().Inverse() .Table(“PostTagMap”); } } }
Listing 8. TagMap class
We are going to use the Repository pattern for database access. We’re using this pattern to decouple the data access code from our controllers and that helps us simplify unit testing our controllers. The core of the repository pattern is an interface that contains the definitions of all the data access methods.
Let’s create an IBlogRepository interface in the JustBlog.Core project with a couple of method definitions.
namespace JustBlog.Core { public interface IBlogRepository { IList Posts(int pageNo, int pageSize); int TotalPosts(); } }
Listing 9. IBlogRepository
The Posts method is used to return the latest published posts based on pagination values. The TotalPosts method is used to return the total number. of published publications. We are going to fill the interface with more methods in the next stories.
Let’s create a class named BlogRepository in the main project and implement the interface.
using JustBlog.Core.Objects; using NHibernate; using NHibernate.Criterion; using NHibernate.Linq; using NHibernate.Transform; using System.Collections.Generic; using System.Linq; namespace JustBlog.Core { public class BlogRepository: IBlogRepository { // NHibernate object private readonly ISession _session; Public BlogRepository(ISession session) { _session = session; } public IList Posts(int page No, int pageSize) { var posts = _session.Query() .Where(p => p.Posted) .OrderByDescending(p => p.PostedIn) .Skip( pageNo * pageSize) .Take(pageSize) .Fetch(p => p.Category) .ToList(); var postIds = posts.Select(p => p.Id).ToList(); return _session.Query() .Where(p => postIds.Contains(p.Id)) .OrderByDescending(p => p.PostedOn) .FetchMany(p => p.Tags) .ToList(); } public int TotalPosts() { return _session.Query().Where(p => p.Published).Count(); } } }
Listing 10. BlogRepository
All calls to the database must be made through the NHibernate ISession object. When we read the post collection using ISession, the Category and Tags dependencies are not populated by default. The Fetch and FetchMany methods are used to tell NHibernate to eagerly fill them.
In the Posts method, we have queried the database twice to get the posts because we need to load all associated tags. We cannot use FetchMany together with the Skip and Take methods in the Linq query. So first we’ve gotten all the posts and then from their ids we’ve queried again to get them with their tags. Please refer to this thread for more information on this issue.
4.4 Configuring Ninject for the JustBlog.Core project
Dependency Injection (DI) helps to avoid instantiating concrete implementations of dependencies within a class. These dependencies are usually injected into a class via the constructor, but sometimes via properties as well. One of the main advantages of dependency injection is unit testing and we will see this when we write unit tests for controllers in Part II.
There are many frameworks available to simplify dependency injection such as Castle Windsor, Unity, Autofac, StructureMap, Ninject, etc. We have chosen Ninject because it is easy to use.
We can install Ninject in the JustBlog.Core project by running the following commands in the Package Manager Console.
If the commands run successfully, we’ll see the Ninject and Ninject.Web.Common assemblies added to the project. Along with the assemblies, a class file named NinjectWebCommon.cs has also been added to the App_Start folder. I’ll explain why we need to install the Ninject.Web.Common extension soon.
We can configure Ninject in the web application using two approaches, using Global.asax.cs or via App_Start. We’re going to use the first approach, so remove the NinjectWebCommon.cs file from the App_Start folder, and also remove any unnecessary references to the WebActivator and Microsoft.Web.Infrastructure assemblies from the project.
The core functionality of any DI framework is to map interfaces to concrete implementations. The mapping of an interface to a particular implementation is called binding. We can group a set of links related to a particular module in Ninject using Ninject Modules. All the bindings and modules are loaded into the core component of Ninject called the Kernel. Whenever the application needs an instance of a concrete class that implements the interface, it is provided by the kernel.
The BlogRepository class has a dependency on Nibernate’s ISession. To create an ISession instance we need the help of another Nibernate interface called ISessionFactory. Let’s create a ninject module class named RepositoryModule that binds both interfaces in the JustBlog.Core project.
using FluentNHibernate.Cfg; using FluentNHibernate.Cfg.Db; using JustBlog.Core.Objects; using NHibernate; using NHibernate.Cache; using ninject; using Ninject.Modules; using Ninject.Web.Common; namespace JustBlog.Core { public class RepositoryModule: NinjectModule { public override void Load() { Bind() .ToMethod ( e => Fluently.Configure() .Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c .FromConnectionStringWithKey) (“JustBlogDbConnString”))) .Cache(c => c.UseQueryCache().ProviderClass()) .Mappings(m => m.FluentMappings.AddFromAssemblyOf()) .ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(true, true, false)) .BuildConfiguration() .BuildSessionFactory() ) .InSingletonScope(); Bind() .ToMethod((ctx) => ctx.Kernel.Get().OpenSession()) .InRequestScope(); } } }
Listing 11. RepositoryModule
To create a Ninject module we need to inherit from the NinjectModule abstract class and implement the Load method. In the Load method, we have mapped (bind) both interfaces to methods using the Bind method.
At a simple level, the Bind method is used to map an interface to a class that implements it.
Eg
Bind().To();
Listing 12. Binding the interface to the class
You could also map the interface to a method that instantiates and returns an implementation of the interface. Binding the interface to a method is very useful when creating an instance requires additional work.
Bind().ToMethod(c => { var foo = new Foo(); return foo; });
Listing 13. Binding the interface to the method
We have used the Fluent API extension methods to create an instance of the ISessionFactory.
Fluently.Configure() .Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey(“JustBlogDbConnString”))) .Cache(c => c.UseQueryCache().ProviderClass()) .Mappings(m => m.FluentMappings.AddFromAssemblyOf()) .ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(true, true, false)) .BuildConfiguration() .BuildSessionFactory()
Listing 14. Configuration via fluent extension methods
Chained extension methods can seem a bit confusing! The following are the things we are doing through those methods.
A. Configuring the database connection string (Database) b. Configuring a provider to cache queries (Cache) c. Specify the assembly where the domain exists and the assignment classes (assignments) d. Ask NHibernate to create tables from the classes (ExposeConfiguration)
There are many extensions available with Ninject and one of them is Ninject.Web.Common which contains some common functionality required for WebForms and MVC applications . We’ve used that extension in the above case to scope the ISession at a request level, that is, until the request completes, Ninject uses the same ISession instance throughout its code. The InRequestScope() extension method lives in the Ninject.Web.Common assembly. We need a single instance of the ISessionFactory throughout the application, so we have limited it to a singleton (InSingletonScope()).
4.5 Configuring Ninject for the MVC Project
All calls to the driver database are simplified through the IBlogRepository interface. To inject an instance of a class that implements IBlogRepository into a controller, we must also configure Ninject in the MVC application. There is an extension (Ninject.Mvc3) available to specifically support MVC applications. We can install it in our MVC project by running the following command.
Successful execution of the command adds the following assemblies to the MVC project.
Ninject Ninject.Web.Common Ninject.Web.Mvc
Delete the NinjectWebCommon.cs file from the App_Start folder and derive our global application class from the NinjectHttpApplication class and override the CreateKernel method.
using Ninject; using Ninject.Web.Common; using System.Web.Mvc; using System.Web.Routing; namespace JustBlog { public class MvcApplication : NinjectHttpApplication { protected override IKernel CreateKernel() { var kernel = new StandardKernel(); kernel.Load(newRepositoryModule()); kernel.Bind().To(); return core; } protected override void OnApplicationStarted() { RouteConfig.RegisterRoutes(RouteTable.Routes); base.OnApplicationStarted(); } } }
Listing 15. Global application class
In the CreateKernel method, we are creating and returning an instance of StandardKernel which is a type of IKernel. IKernel is the kernel of the application where we specify our bindings and when we need an instance of an assigned interface, it provides it.
We’re doing a couple of important things in the StandardKernel object. First, we’re loading an instance of our Repository module that contains all the bindings related to NHibernate interfaces, and then specifying another binding that maps IBlogRepository to BlogRepository directly to it. Finally, the code that we need to execute at application startup must be moved to the OnApplicationStarted method.
That’s all about Ninject configuration work in the application. Let’s start working on controllers and actions.
4.6 Creating Controller and Actions
So far we have concentrated most of our time on building the models, data access classes using NHibernate/Fluent NHibernate and also configuring Ninject for dependency injection. Now is the time to focus on building our MVC project.
Let’s create a controller by right-clicking on the Controllers folder -> Add -> Controller. Name the controller BlogController.
Create a constructor that takes IBlogRepository as an input parameter. Ninject will take care of providing a BlogRepository instance to the BlogController while instantiating it.
using JustBlog.Core; using System.Web.Mvc; namespace JustBlog.Controllers { public class BlogController : Controller { private readonly IBlogRepository _blogRepository; Public BlogController(IBlogRepository blogRepository) { _blogRepository = blogRepository; } } }
Listing 16. BlogController
Create a method named Posts that takes an input parameter p for the page number,
public ViewResult Posts(int p = 1) { // TODO : read and return messages from the repository }
Listing 17. Message action
Any public method in a controller can be called as an action. In general, actions return any type that derives from ActionResult. In the Posts action above, we are going to return a view and for that we have used ViewResult as the return type.
The other types of ActionResult are:
Type Description PartialViewResult Presents a partial view RedirectToRouteResult Issues an HTTP 301 or 302 redirect to a specific action method or route input, generating a URL according to your routing configuration. RedirectResult Issues an HTTP 301 or 302 redirect to a specific URL. ContentResult Returns raw textual data to the browser, optionally setting a content-type header. FileResult Passes binary data (such as a file on disk or a byte array in memory) directly to the browser. JsonResult Presents JSON content to the client. JavaScriptResult Returns a piece of JavaScript source code to be executed by the browser. HttpUnauthorizeResult Set the response HTTP status code to 401 (meaning “Unauthorized”), which causes the active authentication mechanism (Forms Authentication or Windows Authentication) to prompt the visitor to log in. HttpNotFoundResult Returns an HTTP 404 – Not Found error. HttpStatusCodeResult Returns a specified HTTP code. EmptyResult Does nothing. Different types of built-in ActionResults. Source: Pro ASP.NET MVC 3 by Adam & Steven
The p parameter in the Posts action represents the page number. The default value of p is 1 and represents the first page. Inside the action, all we have to do is fetch the most recent posts by calling the Posts method of the IBlogRepository and send them to the view.
Along with the latest posts, we also need to feed the total number. of posts in view that is required to display pagination links in the view. This is a perfect scenario where we can choose to View models.
Create a class named ListViewModel in the Models folder that includes both the collection of active posts and their total number.
using JustBlog.Core; using System.Collections.Generic; namespace JustBlog.Models { public class ListViewModel { public IList Posts { get; private set; } public int TotalPosts { get; private set; } } }
Listing 18. ListViewModel
Here is the initial implementation of the Posts action.
public ViewResult Posts(int p = 1) { // pick the last 10 posts var posts = _blogRepository.Posts(p – 1, 10); var totalPosts = _blogRepository.TotalPosts(); var listViewModel = new ListViewModel { Posts = posts, TotalPosts = totalPosts }; ViewBag.Title = “Latest Posts”; return View(“List”, listViewModel); }
Listing 19. Post Action
We are passing the title to be set to the view via the ViewBag. The ViewBag is a dynamic wrapper around the ViewData dictionary. Instead of instantiating and returning a ViewResult directly, we’ve used the built-in helper method called View to get the job done. The first parameter that we have passed to the View method is the name of the view and the next parameter is the model.
Instead of getting the latest posts via the _blogRepository in the Posts action, we can delegate them to the ListViewModel by passing them in.
public class ListViewModel { public ListViewModel(IBlogRepository _blogRepository, int p) { Posts = _blogRepository.Posts(p – 1, 10); TotalPosts = _blogRepository.TotalPosts(); } public IList Posts { get; private set; } public int TotalPosts { get; private set; } }
Listing 20. ListViewModel
Here is our modified action.
public ViewResult Posts(int p = 1) { var viewModel = new ListViewModel(_blogRepository, p); ViewBag.Title = “Latest Posts”; return View(“List”, view model); }
Listing 21. Post action
Our controller and action are ready and it’s time to work on the view.
4.7 Create View
I have downloaded a free template for our blog from here, you can download the modified css and images of the template from GitHub or the attached source code.
4.7.1 Theme and layout settings
Create a folder named Content in the root of your MVC project. Under Content, Create Folder Themes > Simple. Move the downloaded stylesheet to the simple folder and the images to a new images folder in simple. The MVC project should look like below if you have done everything correctly.
Layout
Like master pages in WebForms, their layouts are in MVC. A site can have as many layouts as you need and they contain the common html content that should appear in all views. Let’s create a new folder called Shared in Views. Right click on the folder and select “Add View”. Type the name of the view as _Layout and uncheck “Use a layout or master page”. We’re going to use the Razor view engine to create the view, so we’ll make sure the “View Engine” dropdown is set to Razor (CSHTML).
Replace the content of _Layout.cshtml as shown below.
SoloBlog
Writings, experiments and more. ..
Listing 22. _Layout.cshtml
The layout co It mainly has four sections Header, Navigation, Content and Footer. There are a couple of interesting things to note. The ViewBag.Title that we passed in from the controller is used in the
We can set the layout for all views globally via _ViewStart.cshtml. As a name, this file is executed before each view is called. Create _ViewStart.cshtml in the Views folder with the following contents.
@{ Layout = “~/Views/Shared/_Layout.cshtml”; }
Listing 23. _ViewStart.cshtml
4.7.2 Fixing anchor links
We have some work to do on the anchor links in the navigation sections.
Listing 24. Section navigation
Same as ASP controls .NET on WebForms, MVC has Html-Helpers. Html-Helpers are lightweight and only used to create simple html controls/elements like forms, textboxes, anchor links, etc.Html helpers are available via the Html property on views.
To create an anchor link named Posts that points to the Posts action, we can do so by
@Html.ActionLink(“Posts”, “Posts”) // (link name, action )
Listing 25. Html.ActionLink
The above razor statement will output the following html,
Listing 26. Generated anchor link
Right now we only have the Posts action, but in the next few parts we’ll create actions to display the contact and about me pages. After replacing the anchors with Html.ActionLinks, the navigation section will be,
List 27. Navigation Section
4.7.3 List View
We can create a view for an action by right-clicking anywhere inside the action and selecting “Add View”. When we create a view, we can strongly bind it to a model and they are called strongly typed views. In the “Add View” window, we have a “Create a strongly typed view” checkbox, and once you check it, you can select the model via the “Model Class” dropdown. Select ListViewModel from the dropdown menu and make sure the “Use a layout or master page” checkbox is checked.
The generated List.cshtml looks like this,
@model ListViewModel @{ ViewBag.Title = “List”; }
Listing 28. List.cshtml
The @model directive at the top of the view says that it is strongly typed with ListViewModel.
The ListViewModel instance that we are passing from the Posts action can be accessed directly through the Model property on the List view.
In our List view, everything that All we have to do is iterate the Posts collection and display them. When displaying each post, we need to display the date it was published, the description, and also the category and tags related to the post.
Let’s create a partial view that shows the details of a single post. Partial views are similar to user controls in WebForms.
We can create a partial view similar to a view. Right-click the shared folder and select Add > View. In the “Add View” window, select the Publish as Model entity and check the “Create as Partial View” checkbox. Name the partial view _PostTemplate.
Add the following content to the _PostTemplate partial view. The explanation follows the markup.
@model JustBlog.Core.Objects.Post
Listing 29. _PostTemplate.cshtml
We want to display the header, category, and tags of the post. as hyperlinks. It would be better if we create custom html helpers to generate the links.
We’ve used three custom html helpers (@Html.PostLink, @Html.CategoryLink, and @Helper.Tags) that generate anchor tags from the post, category, and tags. We could create a custom html helper both declaratively and through code. HTML code helpers are best when the generated html is simple, but in complex cases it is better to opt for declarative helpers. The post header and category links are simple, so we have used code-based HTML helpers, but in the case of tags it is a bit tricky, so we have used a declarative one.
First create the html-helpers for the post title, category and tag (unique) links. Create a new ActionLinkExtensions class file in the root of the MVC project.
using JustBlog.Core.Objects; using the system; using System.Web.Mvc; using System.Web.Mvc.Html; JustBlog namespace { public static class ActionLinkExtensions { public static MvcHtmlString PostLink(this HtmlHelper helper, Post post) { return helper.ActionLink(post.Title, “Post”, “Blog”, new { year = post.PostedOn.Year, month = post.PostedOn.Month, title = post.UrlSlug }, new { title = post.Title }); } public static MvcHtmlString CategoryLink(this HtmlHelper helper, category category) { return helper.ActionLink(category.Name, “Category”, “Blog”, new {category = category.UrlSlug }, new { title = String.Format( “See all posts in {0}”, category.Name) }); } public static MvcHtmlString TagLink(this HtmlHelper helper, Tag Tag) { return helper.ActionLink(tag.Name, “Tag”, “Blog”, new { tag = tag.UrlSlug }, new { title = String.Format(“View all posts in {0}”, tag.Name) }); } } }
Listing 30. ActionLink extensions
Our custom html helpers use ActionLink’s built-in helper method to create the link. Alternatively, we can also build the anchor tag inside the code using the built-in TagHelper class. ActionLink’s built-in wizard has many overloaded versions and the one we have used takes 5 arguments which are described below.
return helper.ActionLink ( post.Title, // anchor text “Post”, // action name “Blog”, // controller name new { // route parameters year = post.PostedOn.Year , month = post .PostedOn.Month, title = post.UrlSlug }, new { // html attributes title = post.Title } );
Listing 31. Custom ActionLink Helper
We’ll see the route parameters when we define routes for our action. Both route parameters and html attributes are passed as anonymous objects.
To use custom extension methods in views, we must specify the namespace (JustBlog) where they exist, either in the views using the @using statement or in the web.config file located at the Views folder. Using the back approach, we can access the extension method on all views.
Listing 32. Specifying the namespace in the View’s web.config file
For reusable declarative html helpers we need to create a cshtml file in the App_Code folder. Let’s create a cshtml file named Helpers.cshtml in the App_Code folder. The important thing to note is that for cshtml files located outside of the Views folder, Html and other View properties are not available, so we need to pass HtmlHelper as an argument to the helper methods.
@helper Tags(System.Web.Mvc.HtmlHelper htmlHelper, IList tags) { foreach (var tag in tags) {
} }
Listing 33. Helpers.cshtml
We can call the Tags method from any view by [file-name].[name-method ] that is, Helpers.Tags(args).
All dates and times in the database will be stored in the UTC (Coordinated Universal Time) time zone (which we will cover in Part II). The advantage of storing dates and times in the UTC time zone is that we can easily convert the dates to a particular time zone. To know more details about this, check out this article. When the dates and times are read from the database, we have to convert them to a particular time zone (to be specified in the configuration) before displaying them to the user.
Let’s create a class called Extensions that contains an extension method ToConfigLocalTime that converts dates and times in the UTC time zone to the time zone specified in the configuration.
using JustBlog.Core.Objects; using the system; using System.Configuration; using System.Web.Mvc; namespace JustBlog { public static class extensions { public static string ToConfigLocalTime(this DateTime utcDT) { var istTZ = TimeZoneInfo.FindSystemTimeZoneById(ConfigurationManager.AppSettings[“TimeZone”]); return String.Format(“{0} ({1})”, TimeZoneInfo.ConvertTimeFromUtc(utcDT, istTZ).ToShortDateString(), ConfigurationManager.AppSettings[“TimezoneAbbr”]); } } }
Listing 34. ToConfigLocalTime method
For example, to display all dates and times in “India Standard Time”, we need to specify the following values in the configuration. Click here for the list of other time zones.
Listing 35. Time zone configuration values
Now, wherever we’re displaying dates and times, we need to call the ToConfigLocalTime extension method on the datetime object.
Listing 36. Displaying the post date
Once our partial view (_PostTemplate) is ready, we can complete our List view. In the list view, all we have to do is iterate through the post collection and render each post using the _PostTemplate partial view.
@model JustBlog.Models.ListViewModel
@ViewBag.Title
@if (Model.Posts.Count > 0) { foreach (var post in Model.Posts ) { @Html.Partial(“_PostTemplate”, post) } } else {
No posts found!
}
Listing 37. List.cshtml
Notice that we have called the partial view _PostTemplate using the Html.Partial method, alternatively we can also use Html.RenderPartial.
Our view is pretty complete. We have some paging and routing work pending to complete this story.
4.7.4 Pagination
We are showing only the last 10 posts and not all. To see previous posts, we need to implement pagination. Let’s show the pagination links (next and previous) both at the top and bottom of the page, this is also the best case where we can get a partial view.
Create a partial view _Pager.cshtml in the Shared folder. What we need to do in the pager partial view is display the previous and next pagination links based on the “total pages” and “current page” values.
Here is the complete code and the explanation follows the code.
@model JustBlog.Models.ListViewModel @* Read current page and total pages *@ @{ var currentPage = ViewContext.RequestContext.HttpContext.Request[“p”] != null ? int.Parse(ViewContext.RequestContext.HttpContext.Request[“p”]) : 1; var totalPages = Math.Ceiling((double)Model.TotalPosts / 10); } @* Check if we need to display pagination links *@ @if (current page > 1 || current page < totalpages) { var p = string.Format("p={0}", currentpage – 1); var n = string.Format("p={0}", currentPage + 1); @* If the view is rendered for the "search" action, add the pagination value with "&" *@ if (ViewContext.RouteData.Values["action"].ToString() .Equals("search", StringComparison .OrdinalIgnoreCase)) { var s = String.Format("?s={0}", ViewContext.RequestContext.HttpContext.Request.QueryString["s"]); p = String.Format("{0}&{1}", s, p); n = String.Format("{0}&{1}", s, n); } else { p = String.Concat("?", p); n = String.Concat("?", n); }
}
Listing 38. _Pager.cshtml
We need to know the total pages and the current page number. to show/hide the pagination links. The former is available directly through the ListViewModel’s TotalPosts property, and the latter can be read from the query string.
In the conditional block, what we are doing is determining the next and previous page number. which will be specified as query strings in anchor links. In search posts (we’ll do this story shortly) there will already be a query string in the URL, so we need to add our pagination value p with “&”, which is taken care of in the inner condition.
Update List.cshtml to display the pagination links at the top and bottom of the page.
@model JustBlog.Models.ListViewModel
@ViewBag.Title
@Html.Partial(“_Pager”, Model) @if (Model.Posts.Count > 0) { foreach (var post in Model.Posts) { @Html.Partial(“_PostTemplate”, post) } } else {
No posts found!
} @Html.Partial(“_Pager” , Model )
Listing 39. List.cshtml
Our list view is complete. Before doing a test drive, we need to fix the routes in RouteConfig.cs in the App_Start folder.
4.7.5 Route Repair
Routes are defined in the RouteConfig.cs file located in the App_Start folder and are used to map incoming requests to controller actions. All routes defined in an application are registered in the RouteTable. You will see an ignored route and map route definitions defined as defaults in the RegisterRoutes method. This RegisterRoutes method is called in the Application_Start event of Global.asax.cs.
using System.Web.Mvc; using System.Web.Routing; JustBlog namespace { public static void RegisterRoutes(RouteCollectionroutes) { routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”); routes.MapRoute( “Default”, “{controller}/{action}/{id}”, new { controller = “Start”, action = “Index”, id = UrlParameter.Optional }); } }
Listing 40. RouteConfig.cs
The IgnoreRoute extension method is used to ignore requests (return 404s) to be handled by the routing infrastructure if they match the pattern. In the above case, all “.axd” requests are not handled by the routing module.
The MapRoute method is used to map an incoming request to a controller and an action. The first default parameter that we have passed in the definition above is an optional name for the route. The second parameter is the pattern and the last one is the default values for the route parameters. If there is no controller or action segment available in the incoming request, they are taken from the default values.
We need to change the default controller and action names specified in the default route to Blog and Posts as shown below,
public static void RegisterRoutes(RouteCollectionroutes) { routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”); routes.MapRoute( “Default”, “{controller}/{action}/{id}”, new { controller = “Blog”, action = “Posts”, id = UrlParameter.Optional }); }
Listing 41. RouteConfig.cs
Before running the application we must create a database named JustBlog and specify the connection string in the web.config.
Listing 42. Specifying the connection string in web.config
You must override the DataSource property according to your SQL server configuration. NHibernate automatically creates the tables when you run the application. You can download the script that inserts some dummy data into the tables from here.
One important thing to do is comment out the following line in the RepositoryModule class in the JustBlog.Core project after the tables have been successfully created. This helps to avoid recreating tables on subsequent requests. You can also manually create tables by running this script.
ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(true, true, false))
Listing 43. RepositoryModule
If everything is OK, you’ll see the following screen.
In our blog application we are not going to have many controllers. We have a BlogController and we will have one more called AdminController in the next part. So, having two controllers in the application, we can ignore the controller name specified in the URL pattern. Instead of http://localhost/blog/posts, we can simply use http://localhost/posts. To achieve this, all we have to do is replace the default route defined in the RegisterRoutes method with the route definition below. Note that the action links we have used in the views will generate the links correctly according to the changes in the routes. That’s why I recommend always using the built-in helper method to generate the links instead of hardcoding them directly in the href attribute.
routes.MapRoute( “Action”, “{action}”, new { controller = “Blog”, action = “Posts” });
Listing 44. Default path
That’s it! We have completed our first story. We can now see the latest posts on our blog and also navigate to older posts.
5. Story #2: Show Posts by Category
Each post belongs to a category and in this story we are going to show posts by category.
To complete this story we have to complete the following tasks.
1. Create repository methods to get posts, total posts, and category based on slug 2. Create an action that returns posts belonging to a category 3. Define a new path in the file RouteConfig.cs
5.1 Create repository methods to get posts, total posts, and slug-based categories
Let’s define three new methods in the IBlogRepository interface.
public interface IBlogRepository { … IList PostsForCategory(string categorySlug, int pageNo, int pageSize); int TotalPostsForCategory(string categorySlug); Category Category(string categorySlug); }
Listing 45. IBlogRepository
The PostsForCategory method returns the latest posts belonging to a category based on slug (UrlSlug) and pagination values. The TotalPostsForCategory method returns the total number. of publications belongs to the category. The Category method returns the category instance.
Here is the implementation of the methods.
public IList PostsForCategory(string categorySlug, int pageNo, int pageSize) { var posts = _session.Query() .Where(p => p.Published && p.Category.UrlSlug.Equals( categorySlug)) .OrderByDescending(p => p.PostedOn) .Skip(pageNo * pageSize) .Take(pageSize) .Fetch(p => p.Category) .ToList(); var postIds = posts.Select(p => p.Id).ToList(); return _session.Query() .Where(p => postIds.Contains(p.Id)) .OrderByDescending(p => p.PostedOn) .FetchMany(p => p.Tags) .ToList(); } public int TotalPostsForCategory(string categorySlug) { return _session.Query() .Where(p => p.Published && p.Category.UrlSlug.Equals(categorySlug)) .Count(); } public Category Category(string categorySlug) { return _session.Query() .FirstOrDefault(t => t.UrlSlug.Equals(categorySlug)); }
Listing 46. BlogRepository
5.2 Create an action to return posts that belong to a particular category
We need a new action in our BlogController to display posts based on the category. Create a new action named Category that takes both the slug parameter and the pagination values.
public ViewResult Category(string category, int p = 1) { // TODO: get the posts for the category and return the view. }
Listing 47. Category action
We can use the same ListViewModel by adding an overloaded constructor that takes the category slug as a parameter. Here is the modified ListViewModel.We also add a new property called Category to store the object.
public class ListViewModel { public ListViewModel(IBlogRepository blogRepository, int p) { Posts = blogRepository.Posts(p – 1, 10); TotalPosts = blogRepository.TotalPosts(); } public ListViewModel(IBlogRepository blogRepository, string categorySlug, int p) { Posts = blogRepository.PostsForCategory(categorySlug, p – 1, 10); TotalPosts = blogRepository.TotalPostsForCategory(categorySlug); Category = blogRepository.Category(categorySlug); } public IList Posts { get; private set; } public int TotalPosts { get; private set; } public Category Category { get; private set; } }
Listing 48. ListViewModel
Our view model is ready, let’s complete the pending work on the action.
public ViewResult Category(string category, int p = 1) { var viewModel = new ListViewModel(_blogRepository, category, p); if (viewModel.Category == null) throw new HttpException(404, “Category not found”); ViewBag.Title = String.Format(@”Latest posts in category “”{0}”””, viewModel.Category.Name); return View(“List”, view model); }
Listing 49. Category action
Sometimes the user may search for posts that belong to a category that doesn’t exist, in which case the Category property will be null. We are throwing a 404 exception if there is no category in the database for the passed slug. We will see in Part III how to handle these exceptions at the application level and display a custom error view to the user.
5.3 Defining a new route in the RouteConfig.cs file
We need to assign requests like http://localhost/category/programming, http://localhost/category/unittesting to Action of category. The default route we currently have does not support this rule. Let’s define a new route above the default that maps these requests to the Category action.
routes.MapRoute( “Category”, “Category/{Category}”, new { controller = “Blog”, action = “Category” });
Listing 50. New path for category action
Our second story is over, now we can search for posts based on category. In the next story we will see how to display tagged posts for a tag.
6. Story #3: Show Posts by Tag
This story is similar to the previous one and in this story we are going to show posts tagged for a particular tag.
The following are the tasks that we are going to perform.
1. Create repository methods to get posts, post totals, and tags based on slug 2. Create an action to return posts for a particular tag 3. Define a new route in the RouteConfig.cs file
6.1 Create repository methods to get posts, post totals and tags based on slug
We need to define three new methods in IBlogRepository.
public interface IBlogRepository { … IList PostsForTag(string tagSlug, int pageNo, int pageSize); int TotalPostsForTag(string tagSlug); Tag Tag(string tagSlug); }
Listing 51. IBlogRepository
Here is the implementation of those methods.
public IList PostsForTag(string tagSlug, int pageNo, int pageSize) { var posts = _session.Query() .Where(p => p.Published && p.Tags.Any(t = > t.UrlSlug.Equals(tagSlug))) .OrderByDescending(p => p.PostedOn) .Skip(pageNo * pageSize) .Take(pageSize) .Fetch(p => p.Category) .ToList(); var postIds = posts.Select(p => p.Id).ToList(); return _session.Query() .Where(p => postIds.Contains(p.Id)) .OrderByDescending(p => p.PostedOn) .FetchMany(p => p.Tags) .ToList(); } public int TotalPostsForTag(string tagSlug) { return _session.Query() .Where(p => p.Published && p.Tags.Any(t => t.UrlSlug.Equals(tagSlug))) .Count( ); } Public Tag Tag(string tagSlug) { return _session.Query() .FirstOrDefault(t => t.UrlSlug.Equals(tagSlug)); }
Listing 52. BlogRepository
6.2 Create an action to return posts for a particular tag
We need to create a new action tag to display posts tagged for a tag .
public ViewResult Tag(string tag, int p = 1) { // TODO: get the posts for the tag and return the view. }
Listing 53. Tag action
We need to modify the overloaded constructor of ListViewModel as shown below to use it in the action tag.
public class ListViewModel { public ListViewModel(IBlogRepository blogRepository, int p) { Posts = blogRepository.Posts(p – 1, 10); TotalPosts = blogRepository.TotalPosts(); } public ListViewModel(IBlogRepository blogRepository, string text, string type, int p) { switch (type) { case “Tag”: Posts = blogRepository.PostsForTag(text, p – 1, 10); TotalPosts = blogRepository.TotalPostsForTag(text); Tag = blogRepository.Tag(text); break; default: Posts = blogRepository.PostsForCategory(text, p – 1, 10); TotalPosts = blogRepository.TotalPostsForCategory(text); Category = blogRepository.Category(text); break; } } public IList Posts { get; private set; } public int TotalPosts { get; private set; } public Category Category { get; private set; } public tag Tag { get; private set; } }
Listing 54.ListViewModel
We’ve modified the constructor to pass the additional parameter type it represents to get posts based on category or tag. We also added a new property tag.
Here is the implementation of the Tag action. We are passing the type as “Tag” to get the posts tagged for the corresponding tag.
public tag ViewResult(string tag, int p = 1) { var viewModel = new ListViewModel(_blogRepository, tag, “Tag”, p); if (viewModel.Tag == null) throws a new HttpException(404, “Tag not found”); ViewBag.Title = String.Format(@”Last posts tagged in “”{0}”””, viewModel.Tag.Name); return View(“List”, view model); }
Listing 55. Tag action
Since we’ve changed the constructor of ListViewModel, we need to update the Category action to pass the type as “Category”.
public ViewResult Category(string category, int p = 1) { var viewModel = new ListViewModel(_blogRepository, category, “Category”, p); if (viewModel.Category == null) throw new HttpException(404, “Category not found”); ViewBag.Title = String.Format(@”Latest posts in category “”{0}”””, viewModel.Category.Name); return View(“List”, view model); }
Listing 56. Category action
6.3 Defining a new route in the RouteConfig.cs file
As in the previous story, we need to add another route to direct the requests to the Tag action.
routes.MapRoute( “Tag”, “Tag/{tag}”, new { controller = “Blog”, action = “Tag” });
Listing 57. Labeling path
We’ve also completed this story. In the next story we will see how to search for publications.
7. Story #4 – Search Posts
In this story, we’re going to implement search functionality for our blog.
The following are the tasks that we are going to execute as part of this story.
1. Create a partial view to display the search text box 2. Create the necessary repository methods 3. Create a search action
7.1 Create a partial view to display the search text box
Create a new partial view named _Search.cshml in the Shared folder. We need to create an html form and that can be easily accomplished with the html helper Html.BeginForm().
@using (Html.BeginForm( “Find”, // action “Blog”, // FormMethod.Get controller, // new method { id = “find-form” })) // html attributes {
@Html.TextBox(“s”)
}
Listing 58. Search form
We want to display the search form on all pages, so let’s update the _Layout.cshtml to include the _Search.cshtml partial view on top of the RenderBody.
…
…
Listing 59. _Layout.cshtml
7.2 Create the necessary repository methods
We need to create two methods, one method to find the posts matching the search text and the other to display the total number of posts matching the search text. search for pagination.
public interface IBlogRepository { … IList PostsForSearch(string search, int pageNo, int pageSize); int TotalPostsForSearch(string search); }
Listing 60. IBlogRepository
Here is the implementation of both methods.
public IList PostsForSearch(string search, int pageNo, int pageSize) { var posts = _session.Query() .Where(p => p.Published && (p.Title.Contains(search ) || p.Category.Name.Equals(search) || p.Tags.Any(t => t.Name.Equals(search)))) .OrderByDescending(p => p.PostedOn) .Skip(pageNo * pageSize) .Take(pageSize) .Fetch(p => p.Category) .ToList(); var postIds = posts.Select(p => p.Id).ToList(); return _session.Query() .Where(p => postIds.Contains(p.Id)) .OrderByDescending(p => p.PostedOn) .FetchMany(p => p.Tags) .ToList(); } public int TotalPostsForSearch(string search) { return _session.Query() .Where(p => p.Published && (p.Title.Contains(search) || p.Category.Name.Equals(search) | p.Tags.Any(t => t.Name.Equals(find)))) .Count(); }
Listing 61. BlogRepository
If you see the implementation of the PostsForSearch method, we’re looking for posts that match the text, either in title, category, or tags.
7.3 Create Search Action
We need an action that will display the posts that match the search text and is almost the same as the Category and Tag actions.
public ViewResult Search(string s, int p = 1) { ViewBag.Title = String.Format(@”Post lists found for search text “”{0}”””, s); var viewModel = new ListViewModel(_blogRepository, s, “Search”, p); return View(“List”, view model); }
Listing 62. Search Action
Let’s update the ListViewModel.
public class ListViewModel { public ListViewModel(IBlogRepository blogRepository, int p) { Posts = blogRepository.Posts(p – 1, 10); TotalPosts = blogRepository.TotalPosts(); } public ListViewModel(IBlogRepository blogRepository, string text, string type, int p) { switch (type) { case “Category”: Posts = blogRepository.PostsForCategory(text, p – 1, 10); TotalPosts = blogRepository.TotalPostsForCategory(text); Category = blogRepository.Category(text); break; case “Tag”: Posts = blogRepository.PostsForTag(text, p – 1, 10); TotalPosts = blogRepository.TotalPostsForTag(text); Tag = blogRepository.Tag(text); break; default: Posts = blogRepository.PostsForSearch(text, p – 1, 10); TotalPosts = blogRepository.TotalPostsForSearch(text); search = text; break; } } public IList Posts { get; private set; } public int TotalPosts { get; private set; } public Category Category { get; private set; } public tag Tag { get; private set; } public string Find { get; private set; } }
Listing 63. ListViewModel
We’ve added a new Search property to the ListViewModel to store the search text and display it in the view.
We don’t want the form to submit when the Search button is clicked without entering any text in the search box and we can achieve this via a simple script. Create a folder named Scripts. Add an app.js script file below the folder. All we have to do in the script file is listen for the form submit event and stop the event if there is no text in the search box.
$(function () { $(‘#searchform’).submit(function () { if ($(“#s”).val().trim()) returns true; returns false; }); });
Listing 64. app.js
Don’t forget to include the app.js script file in the _Layout.cshtml page along with the jquery library.
Listing 65. Script references in _Layout.cshtml
8. Story #5: Display the details of a single post
The stories we’ve completed so far are about displaying a collection of posts based on category, tag, or search text. In this story we will see how to display the details of a single post.
The following are the tasks that we are going to execute as part of this story.
1. Create a repository method to return the post based on year, month, and url-slug 2. Create an action to return the post view 3. Create view 4. Define a new route in RouteConfig.cs
8.1 Create a repository method to return the post based on year, month, and URL slug
Each post is uniquely identified by its title slug (UrlSlug) along with the year and month of publication. It’s published. Define a new method in the IBlogRepository that returns a post based on those three parameters.
public interface IBlogRepository { … Post post (int year, int month, string titleSlug); }
Listing 66. IBlogRepository
Implement the Post method on the BlogRepository.
public Post Post(int year, int month, string titleSlug) { var query = _session.Query() .Where(p => p.Posted.Year == year && p.Posted.Month = = month && p.UrlSlug.Equals(titleSlug)) .Fetch(p => p.Category); query.FetchMany(p => p.Tags).ToFuture(); return query.ToFuture().Single(); }
Listing 67. BlogRepository
8.2 Create an action to return the view of the post
Create a new action named Post with year, month, and title (UrlSlug)
public ViewResult Post(int year, int month, string title) { var post = _blogRepository.Post(year, month, title); if (post == null) throw new HttpException(404, “Post not found”); if (post.Published == false && User.Identity.IsAuthenticated == false) throw new HttpException(401, “Post is not published”); return View(post); }
Listing 68. Publish action
If the post is not published and the user is not an administrator, we are throwing a 401 exception. The administrator needs to see the post even though it is not published.
8.3 Create View
We need a separate view for the Post action with the same name. We could have used the _PostTemplate partial view but there are some differences in the html so I decided to keep things separate.
@model JustBlog.Core.Objects.Post @{ ViewBag.Title = Model.Title; }
Listing 69. Post view
8.4 Defining a new route in RouteConfig.cs
The URL of a post looks like http://localhost/archive/2012/ 11/some_publication. We have three variable segments (year, month, and title) in the application. Let’s define a new route to handle these requests.
routes.MapRoute( “Post”, “File/{year}/{month}/{title}”, new { controller = “Blog”, action = “Post” });
Listing 70.Post Path
In future stories, we’ll see how to display sidebar widgets using a specific ASP.NET MVC feature called Secondary Action.
9. Story #6: Displaying Post Categories in a Widget
We need to display a set of widgets as a sidebar on our blog. In this story, we’re going to create the categories widget that displays all of the blog’s categories. In the following stories, we will see how to create other widgets to display tags and latest posts.
The following are the tasks that we have to execute to complete this story.
1. Create a repository method that returns all categories 2. Create a view model 3. Create a child action 4. Create the necessary partial views
9.1 Create a method repository method that returns all categories
Define and implement the Categories method that returns all categories in the database.
public interface IBlogRepository { … IList Categories(); }
Listing 71. IBlogRepository
The implementation is quite simple,
public IList Categories() { return _session.Query().OrderBy(p => p .Name).ToList(); }
Listing 72. BlogRepository
9.2 Creating a view model
Let’s display the entire sidebar via a single action. We need a new single view model that wraps all the widget data. Let’s create a new view model called WidgetViewModel in the Models folder.
using JustBlog.Core; using JustBlog.Core.Objects; using System.Collections.Generic; namespace JustBlog.Models { public class WidgetViewModel { public WidgetViewModel(IBlogRepository blogRepository) { Categories = blogRepository.Categories(); } public IList Categories { get; private set; } } }
Listing 73. WidgetViewModel
Currently, our WidgetViewModel contains only property categories of type IList, we’ll be adding other widget data as well soon.
9.3 Creating a child action
A child action is an action that can be called from a view and cannot be called directly from the browser. We can make a normal action a child action by marking it with the ChildActionOnly attribute.
[ChildActionOnly] public PartialViewResult Sidebars() { var widgetViewModel = new WidgetViewModel(_blogRepository); return PartialView(“_Sidebars”, widgetViewModel); }
Listing 74. Sidebars secondary action
Since we are returning a partial view, the return type of the action is specified as PartialViewResult.
9.4 Create the necessary partial views
Create two partials _Sidebars and _Categories in the Shared folder. _Sidebars calls _Categories and the other partial views passing the appropriate model.
@model JustBlog.Models.WidgetViewModel
Listing 75. _Sidebars.cshtml
Here is the content of _Categories.cshtml. Note that our _Categories.cshtml is strongly typed with IList . All we do in the markup below is iterate the model and render it as an unordered list.
@model IList
Listing 76. _Categories. cshtml
Update _Layout.cshtml to call the Sidebars secondary action.
…
…
Listing 77. _Layout.cshtml
If you run the app, you can see the Categories widget in the sidebar.
Let’s fill in the other widgets.
10. Story #7 – Display post tags in a widget
This story is quite similar to the previous one.
1. Create a bucket method that returns all tags 2. Update the WidgetViewModel 3. Create a partial view to display the tags
10.1 Create a bucket method that returns all tags tags
public interface IBlogRepository { … IList Tags(); }
Listing 78. IBlogRepository
public IList Tags() { return _session.Query().OrderBy(p => p.Name).ToList(); }
Listing 79. BlogRepository
10.2 Update the WidgetViewModel
Add a new Tags property to the WidgetViewModel to store the collection of tags.
public class WidgetViewModel { public WidgetViewModel(IBlogRepository blogRepository) { Categories = blogRepository.Categories(); Tags = blogRepository.Tags(); } public IList Categories { get; private set; } Public IList
Listing 80. WidgetViewModel
10.3 Create a partial view to display the tags
Create a strongly typed _Tags.cshtml partial view with IList.
@model IList
Listing 81. _Tags.cshtml
We need to update _Sidebars.cshtml to render the tags as shown below .
@model JustBlog.Models.WidgetViewModel
Listing 82. _Sidebars.cshtml
11. Story #8 – Displaying the latest posts in a widget
We already have the repository method that returns the latest posts. Add a new property LatestPosts to the WidgetViewModel to store the result.
public class WidgetViewModel { public WidgetViewModel(IBlogRepository blogRepository) { Categories = blogRepository.Categories(); Tags = blogRepository.Tags(); Most recent posts = blogRepository.Posts(0, 10); } public IList Categories { get; private set; } Public IList
Listing 80. WidgetViewModel
Create a partial view named _LatestPosts.cshtml and paste the content below.
@model IList
Listing 83 ._Latest Posts .cshtml
Update _Sidebars.cshtml.
@model JustBlog.Models.WidgetViewModel
Listing 84. _Sidebars.cshtml
12. Summary
Great! We have completed all the stories in Part 1. Let’s summarize the things we have completed in this part. We have created the domain entities, database classes, and other components that are part of the model layer. We have configured Fluent NHibernate and NHibernate for database interactions and Ninject for dependency injection. We have completed the stories showing posts based on category, tag. We implemented search functionality for our blog. We also populated stories to display single post details and sidebar widgets.
In the next part, we’ll create an admin console to manage posts, categories, and tags. We’re going to learn some cool stuff like how to implement forms authentication, how to write unit tests for controllers, and much more. Do not miss it!
Your comments and suggestions are very valuable to me, so please share a comment!
Download the Source Fork on Github
.