How to create a simple blog using ASP.NET MVC - Part III

Table of Contents

1. Introduction

Our JustBlog is in a pretty good state. Yep! still we got some important things to accomplish before we think about release. One very important thing that's missing is the "commenting system". In this final part, we are going to see about integrating Disqus commenting system to our blog. Also, we will integrate two more services: AddThis and Feedburner, for sharing and subscriptions.

Honestly, the way we referenced Scripts and CSS in views are not good. We are going to tidy them up by utilizing the bundling and minification feature. We also completely missed exception handling! Luckily, we got ELMAH!! which is an error handling/logging library that takes care of the job.

Before we wind up, we will see how to create a contact page and also we'll take necessary steps to make our blog SEO friendly.

JustBlog Part III

JustBlog Part III

2. Part III - Integrate Disqus, AddThis and Feedburner. Bundling and minifying assets. Exception handling using ELMAH. Contact page and more.

Following are the user stories we are going to finish in this part.

2.1 User Stories

Story #1 - Integrate Disqus commenting system

Story #2 - Integrate AddThis and Feedburner

Story #3 - Scripts, CSS bundling and minification

Story #4 - Implement exception handling using ELMAH

Story #5 - Implement Contact page

Story #6 - SEO optimization

3. Story #1 - Integrate Disqus commenting system

Disqus is a popular commenting system used in lot of sites. It helps to create online communities for any website through it's powerful commenting service. Most important thing is.. it's free!

Some of the stunning features provided by Disqus are,

a. Real-time discussion
b. Login using Google+, Facebook and others
c. Email notifications
d. Spam filtering
e. Moderation panel
f. Comments import/export and more.

There are also other popular commenting systems available in the market like LiveFyre, IntenseDebate, Echo etc. It's worth to check them out.

To complete this story, we have to finish the following tasks.

1. Register with Disqus
2. Integrate Disqus in blog
3. Modify views to display comments count

Let's start the work.

3.1 Register with Disqus

To use Disqus, we have to create an account and register the site in Disqus. Next, we have to copy couple of script blocks that should be included in our views to allow users to post comment and also to display the total no. of comments each post has received.

Let's go to the Disqus home page,

Disqus home page

Disqus home page

There is an orange button at the right saying "Get this on your site". By clicking that we will be taken to the registration page.

Disqus registration

Disqus registration

The left-column is to register the site and the right one is to create an account.

If your site is www.justblog.com then you have to enter justblog.com in the Site URL field. You can give any name for the Site Name and Site ShortName fields but I recommend to give the website domain name itself i.e. justblog.

Disqus registration

Disqus registration

On filling both the forms and clicking the Continue button, two actions takes place at the Disqus side. First, our site is successfully registered and next we have a free account to manage our site comments.

Followingly, we will be taken to a new page where we can choose the platform to install Disqus.

Choosing platform

Choosing platform

Each icon button represents a platform supported by Disqus. The "Universal Code" button is for any site created on any platform. That's what we need in our case! On clicking that button we'll be taken to a new page that contains code snippets.

Copy scripts

Copy scripts

Two code blocks (mostly javascript) are displayed in the page. The first one is used to render Disqus commenting form in a page where anyone can login and post comments. The second one is used to display the total comments for each post. We can copy those code blocks at anytime from Disqus. Before copy-paste, let's explore the code first!

Here comes the first one. This block has to be included in every page where we need users to post comments.

<div id="disqus_thread"></div>

<script type="text/javascript">
	/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
	var disqus_shortname = 'justblog'; // required: replace example with your forum shortname

	/* * * DON'T EDIT BELOW THIS LINE * * */
	(function() {
		var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
		dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
		(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
	})();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>

Listing 1. Code block that loads Disqus in a page

The code block has a <div> element with id "disqus_thread" and a script block. Disqus is loaded in an iframe inside the <div> element. It's the script block that does all the magic!

The script block is divided into two parts. The first part (which is under the first comment) is where we can set the configuration variables and second part is what we should not touch!

At the moment we see a single configuration variable disqus_shortname (which is the unique site short name that we entered while registering the site) in the code snippet. Actually there are more configuration variables out there and knowing some of them is really important.

3.1.1 disqus_indentifier

This is an unique identifier used to identify an article. When the page is loaded, this identifier is used to lookup the correct thread and load the respective comments. If the parameter is not specified then the current url is taken as the identifier.

Delegating the identification to the page url is often not reliable so it's highly recommended to use a custom identifier. To more about this variable visit this page.

3.1.2 disqus_title

This is an optional parameter used to specify the title for an article. If not specified Disqus will use the <title> attribute of the page. If that also not exists then Disqus uses the page url as the title.

3.1.3 disqus_url

Tells the Disqus, the URL for the article. If not specified, Disqus will use the URL of the current page. It also highly recommended to specify this parameter instead of leaving it to the page url.

To gain more knowledge on these and other configuration variables visit this page.

Below is the second script block that's used to display the comments count for each post.

<script type="text/javascript">
	/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
	var disqus_shortname = 'justblog'; // required: replace example with your forum shortname

	/* * * DON'T EDIT BELOW THIS LINE * * */
	(function () {
		var s = document.createElement('script'); s.async = true;
		s.type = 'text/javascript';
		s.src = '//' + disqus_shortname + '.disqus.com/count.js';
		(document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
	}());
</script>

Listing 2. Code block that displays total comments

To display the total comments near to each post we have to create an anchor element having href as the post identifier appended with "#disqus_thread".

For example,

<a href="http://foo.com/bar.html#disqus_thread">Link</a>

Listing 3. Anchor element that displays comments count

Disqus reads all the anchor elements having href ends with "#disqus_thread"; retrieve the identifier (in the above example it is http://foo.com/bar.html) from each element; make a single AJAX call to the server to get the comments count for all the posts in the page and replace those anchor elements inner-html with their respective count values.

Ok, enough theory! Let's copy the code blocks and paste into our MVC application views.

3.2 Integrate Disqus in blog

We have to display the Disqus commenting form in the page that displays the complete post. Instead of directly pasting the copied code to the view Post.cshtml let's wrap that in a separate partial view.

Create a partial view _Disqus.cshtml under Shared folder and paste the contents of the first code block. Strongly-type the partial view to Post class because we needed that to create unique identifier for the post.

@model JustBlog.Core.Objects.Post

<div id="disqus_thread"></div>
<script type="text/javascript">

    /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
    var disqus_shortname = '@System.Configuration.ConfigurationManager.AppSettings["Domain"]'; // required: replace example with your forum shortname

    var disqus_identifier =  '@Model.Href(Url)';

	var disqus_url = '@string.Format("http://{0}.com{1}", System.Configuration.ConfigurationManager.AppSettings["Domain"], Model.Href(Url))';

    /* * * DON'T EDIT BELOW THIS LINE * * */
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = false;
        dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>

Listing 4. _Disqus.cshtml

We have done three important changes to the copied code from Disqus.

First, instead of hardcoding the disqus_shortname we are loading it from the configuration.

Second, we are setting the url of the post excluding the domain name as the disqus_identifier (ex. /archive/2013/5/learn_jquery_in_30_minutes). This helps us to load the same comments for the article even if the domain name of the blog changes in future.

We have created an extension method for Post class (Extensions.cs) to return the url without the domain name.

public static string Href(this Post post, UrlHelper helper)
{
	return helper.RouteUrl(new { controller = "Blog", action = "Post", year = post.PostedOn.Year, month = post.PostedOn.Month, title = post.UrlSlug });
}

Listing 5. Extension method that returns relative path of post

Third thing is, we are setting the disqus_url manually instead of allowing Disqus taking the current url (window.location) automatically. The reason why have to do this is if the url contains querystring then there are more chances Disqus fails to load the relevant comments for the article.

Now update Post.cshtml to include _Disqus.cshtml and we are done!

@model JustBlog.Core.Objects.Post

@{
  ViewBag.Title = Model.Title;
}

<div id="content">
  <div class="post">
    <div class="post-meta">
      <div class="row">
        <div class="post-title">
          <h1>@Html.PostLink(Model)</h1>
        </div>
      </div>
      <div class="row">
        <div class="post-category">
          <span>Category:</span> @Html.CategoryLink(Model.Category)
        </div>
        <div class="post-tags">
          <span>Tags:</span> @Helpers.Tags(Html, Model.Tags)
        </div>
        <div class="posted-date">
          @Model.PostedOn.ToConfigLocalTime()
        </div>
      </div>
    </div>
    <div class="post-body">
     @Html.Raw(Model.Description)
    </div>

	@* INCLUDE DISQUS *@
    @Html.Partial("_Disqus")
  </div>
</div>

Listing 6. Post.cshtml

If you run the application and browse to any article you should see Disqus loaded right at the bottom.

Disqus commenting form

Disqus commenting form

Running Disqus in developer mode

You can test Disqus commenting system at development time by setting the configuration variable "disqus_developer" to "1".

3.3 Modify views to display comments count

Usually we wish to display the total comments each article has received. Displaying the comments count let users to know how popular the article is! In our application, we have to display the comments count in two pages: List (home page where we display the list of recent articles) and Post (the page that displays the complete details of a single post).

As I already told, to display the comments count we have to do two things.

First, we have to create an <a> element with href belongs to the particular post's unique identifier + "#disqus_thread".

Second, include the second code block to the views that we have copied from the Disqus page.

Update the Post.cshtml to include the <a> element.

@model JustBlog.Core.Objects.Post

@{
  ViewBag.Title = Model.Title;
}

<div id="content">
  <div class="post">
    <div class="post-meta">
      <div class="row">
        <div class="post-title">
          <h1>@Html.PostLink(Model)</h1>
        </div>
      </div>
      <div class="row">
        <div class="post-category">
          <span>Category:</span> @Html.CategoryLink(Model.Category)
        </div>
        <div class="post-tags">
          <span>Tags:</span> @Helpers.Tags(Html, Model.Tags)
        </div>

        <!-- DISPLAY THE COMMENTS COUNT -->
        <div class="no-of-comments">
          <a href="@string.Format("{0}#disqus_thread", Model.Href(Url))"></a>
        </div>

        <div class="posted-date">
          @Model.PostedOn.ToConfigLocalTime()
        </div>
      </div>
    </div>
    <div class="post-body">
     @Html.Raw(Model.Description)
    </div>
    @Html.Partial("_Disqus")
  </div>
</div>

Listing 7. Post.cshtml

We also have to update the partial view _Disqus.cshtml to include the second code block beneath the existing code.

<div id="disqus_thread"></div>

<script type="text/javascript">
    /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
    var disqus_shortname = '@System.Configuration.ConfigurationManager.AppSettings["Domain"]'; // required: replace example with your forum shortname
    var disqus_identifier =  '@Model.Href(Url)';
	var disqus_url = '@string.Format("http://{0}.com{1}", System.Configuration.ConfigurationManager.AppSettings["Domain"], Model.Href(Url))';

    /* * * DON'T EDIT BELOW THIS LINE * * */
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = false;
        dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>

<script type="text/javascript">
  /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
  var disqus_shortname = '@System.Configuration.ConfigurationManager.AppSettings["Domain"]'; // required: replace example with your forum shortname

  /* * * DON'T EDIT BELOW THIS LINE * * */
  (function () {
    var s = document.createElement('script'); s.async = true;
    s.type = 'text/javascript';
    s.src = 'http://' + disqus_shortname + '.disqus.com/count.js';
    (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
  }());
</script>

Listing 8. _Disqus.cshtml

Now if you visit any article you could see the comments count displayed right at the top.

Displaying Disqus comments count

Displaying Disqus comments count

We have to display the comments count in List page as well. For that, update the _PostTemplate.cshtml to include the comment link.

@model JustBlog.Core.Objects.Post

<div class="post">
  <div class="post-meta">
    <div class="row">
      <div class="post-title">
        <h2>@Html.PostLink(Model)</h2>
      </div>
    </div>
    <div class="row">
      <div class="post-category">
        <span>Category: </span>@Html.CategoryLink(Model.Category)
      </div>
      <div class="post-tags">
        <span>Tags:</span>@Helpers.Tags(Html, Model.Tags)
      </div>

	  <!-- DISPLAY THE COMMENTS COUNT -->
      <div class="no-of-comments">
        <a href="@string.Format("{0}#disqus_thread", Model.Href(Url))"></a>
      </div>

      <div class="posted-date">
        @Model.PostedOn.ToConfigLocalTime()
      </div>
    </div>
  </div>
  <div class="post-body">
    @Html.Raw(Model.ShortDescription)
  </div>
  <div class="post-foot">
    @Html.ActionLink("continue...", "post", "blog", new { year = Model.PostedOn.Year, month = Model.PostedOn.Month, day = Model.PostedOn.Day, title = Model.UrlSlug }, new { title = "continue..." })
  </div>
</div>

Listing 9. _PostTemplate.cshtml

Copy-paste the comment code block to the List.cshtml page.

@model JustBlog.Models.ListViewModel

<div id="content">

  <h1>@ViewBag.Title</h1>

  @Html.Partial("_Pager", Model)

  @if (Model.Posts.Count > 0)
  {
    foreach (var post in Model.Posts)
    {
    @Html.Partial("_PostTemplate", post)
    }
  }
  else
  {
    <p>No posts found!</p>
  }

  @Html.Partial("_Pager", Model)
</div>
<script type="text/javascript">
  /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
  var disqus_shortname = '@System.Configuration.ConfigurationManager.AppSettings["Domain"]'; // required: replace example with your forum shortname

  /* * * DON'T EDIT BELOW THIS LINE * * */
  (function () {
    var s = document.createElement('script'); s.async = true;
    s.type = 'text/javascript';
    s.src = 'http://' + disqus_shortname + '.disqus.com/count.js';
    (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
  }());
</script>

Listing 10. List.cshtml

That's it. We've successfully integrated Disqus to our blog. Now anyone can post comments and also see how many comments each post has got. You can manage the comments like deleting, editing etc. through the moderation panel available in Disqus. Let's take a break and work on the next story!

4. Story #2 - Integrate AddThis and Feedburner

In this story, we are going to integrate two widely used online services to our blog: AddThis and Feedburner.

4.1 AddThis

AddThis is a widely used service for sharing content online. At the time of writing this article, more than millions of sites uses this service. AddThis not only helps to share content easily but also provides an analytics tool to track the user activities like shares, clicks etc.

AddThis

AddThis

To integrate AddThis, we have to complete the following tasks.

1. Create an account in AddThis
2. Grab the AddThis code
3. Update the views to show the AddThis widget

4.1.1 Create an account in AddThis

To integrate AddThis, first we have to create an account.

AddThis registration

AddThis registration

We can also register through facebook, twitter or other services. Once we registered we can grab the code that's needed to display the AddThis widget.

4.1.2 Grab the AddThis code

By clicking the "Get The Code" button at the top we will be taken to a new page where we can select the desired widget and copy the code.

Grab the code

Grab the code

As default, the "Get Sharing Buttons for" in the left column is set to "Website". Leave that as it is because that's what we needed. You can select a variety of styles from the "Select style" group. On selecting each style, the preview and the required code is shown at right. I've selected the second style for our blog. You can select whatever you like and copy the code.

4.1.3 Update the views to show the AddThis widget

Let's create a new partial view under the Shared folder called _AddThis.cshtml. Paste the copied code from the AddThis website into it.

<!-- AddThis Button BEGIN -->
<div class="addthis_toolbox addthis_default_style addthis_32x32_style">
<a class="addthis_button_preferred_1"></a>
<a class="addthis_button_preferred_2"></a>
<a class="addthis_button_preferred_3"></a>
<a class="addthis_button_preferred_4"></a>
<a class="addthis_button_compact"></a>
<a class="addthis_counter addthis_bubble_style"></a>
</div>
<script type="text/javascript">var addthis_config = {"data_track_addressbar":true};</script>
<script type="text/javascript" src="http://s7.addthis.com/js/300/addthis_widget.js#pubid=ra-519360bf195ef191"></script>
<!-- AddThis Button END -->

Listing 11. _AddThis.cshtml

We have to show the AddThis widget in two places: List and Post pages. For the List page, we have to update the _PostTemplate.cshtml partial view (under Shared folder) and for the Post page we have to update the Post.cshtml. Below are the updated views.

4.1.3.1 _PostTemplate.cshtml

@model JustBlog.Core.Objects.Post

<div class="post">
  <div class="post-meta">
    <div class="row">
      <div class="post-title">
        <h2>@Html.PostLink(Model)</h2>
      </div>

      <!-- AddThis -->
      <div class="share-links">
        @Html.Partial("_AddThis")
      </div>

    </div>
    <div class="row">
      <div class="post-category">
        <span>Category: </span>@Html.CategoryLink(Model.Category)
      </div>
      <div class="post-tags">
        <span>Tags:</span>@Helpers.Tags(Html, Model.Tags)
      </div>
      <div class="no-of-comments">
        <a href="@string.Format("{0}#disqus_thread", Model.Href(Url))"></a>
      </div>
      <div class="posted-date">
        @Model.PostedOn.ToConfigLocalTime()
      </div>
    </div>
  </div>
  <div class="post-body">
    @Html.Raw(Model.ShortDescription)
  </div>
  <div class="post-foot">
    @Html.ActionLink("continue...", "post", "blog", new { year = Model.PostedOn.Year, month = Model.PostedOn.Month, day = Model.PostedOn.Day, title = Model.UrlSlug }, new { title = "continue..." })
  </div>
</div>

Listing 12. _PostTemplate.cshtml

4.1.3.2 Post.cshtml

@model JustBlog.Core.Objects.Post

@{
  ViewBag.Title = Model.Title;
}

<div id="content">
  <div class="post">
    <div class="post-meta">
      <div class="row">
        <div class="post-title">
          <h1>@Html.PostLink(Model)</h1>
        </div>

       <!-- AddThis -->
        <div class="share-links">
          @Html.Partial("_AddThis")
        </div>

      </div>
      <div class="row">
        <div class="post-category">
          <span>Category:</span> @Html.CategoryLink(Model.Category)
        </div>
        <div class="post-tags">
          <span>Tags:</span> @Helpers.Tags(Html, Model.Tags)
        </div>
        <div class="no-of-comments">
          <a href="@string.Format("{0}#disqus_thread", Model.Href(Url))"></a>
        </div>
        <div class="posted-date">
          @Model.PostedOn.ToConfigLocalTime()
        </div>
      </div>
    </div>
    <div class="post-body">
      @Html.Raw(Model.Description)
    </div>
    @Html.Partial("_Disqus")
  </div>
</div>

Listing 13. Post.cshtml

If you run the application and visit either the List or Post page, you could see the AddThis widget displayed below the post heading as shown below.

Integrating AddThis to site

Integrating AddThis to site

On including the AddThis widget in our views some scripts are downloaded in the background. When you click any of the icon in the widget like Facebook, twitter etc. AddThis invokes the corresponding service provider passing the current page url. This behavior is acceptable in the Post page but not in the List page. On clicking the share icon in the List page, the url of the corresponding post has to passed instead of the current url.

We can override the url and other parameters that has to be passed on clicking the share icons by setting some configuration attributes in the anchor elements.

The main attributes are:

addthis:url - URL to use if not the current page. This is helpful when you have an AddThis button on multiple articles on the same page.

addthis:title - This is an alternate title.

addthis:description - This is an alternate description. As default, AddThis uses the page's MetaDescription.

To know more about these parameters, visit this page

4.1.3.3 Configuring AddThis

Update the _AddThis.cshtml partial view to set those attributes. We need the url, title and description of the post that has to be shared inside the partial view; for that, we have to strongly-type the view to Post model.

@model JustBlog.Core.Objects.Post

<!-- AddThis Button BEGIN -->
<div class="addthis_toolbox addthis_default_style addthis_32x32_style">
<a class="addthis_button_preferred_1" addthis:url="@(string.Format("http://www.prideparrot.com{0}", Model.Href(Url)))" addthis:title="@Model.Title" addthis:description="@Model.Meta"></a>
<a class="addthis_button_preferred_2" addthis:url="@(string.Format("http://www.prideparrot.com{0}", Model.Href(Url)))" addthis:title="@Model.Title" addthis:description="@Model.Meta"></a>
<a class="addthis_button_preferred_3" addthis:url="@(string.Format("http://www.prideparrot.com{0}", Model.Href(Url)))" addthis:title="@Model.Title" addthis:description="@Model.Meta"></a>
<a class="addthis_button_preferred_4" addthis:url="@(string.Format("http://www.prideparrot.com{0}", Model.Href(Url)))" addthis:title="@Model.Title" addthis:description="@Model.Meta"></a>
<a class="addthis_button_compact" addthis:url="@(string.Format("http://www.prideparrot.com{0}", Model.Href(Url)))" addthis:title="@Model.Title" addthis:description="@Model.Meta"></a>
<a class="addthis_counter addthis_bubble_style" addthis:url="@(string.Format("http://www.prideparrot.com{0}", Model.Href(Url)))" addthis:title="@Model.Title" addthis:description="@Model.Meta"></a>
</div>
<script type="text/javascript">var addthis_config = {"data_track_addressbar":true};</script>
<script type="text/javascript" src="http://s7.addthis.com/js/300/addthis_widget.js#pubid=ra-519360bf195ef191"></script>
<!-- AddThis Button END -->

Listing 14. _AddThis.cshtml

Yes, we've completed the AddThis integration successfully. Now anyone can share the posts! Let's work on the next story that help users to subscribe to our blog.

4.2 Feedburner

To let users subscribe to our blog first we have to create a feed.

Feed

A web feed (or news feed) is a data format used for providing users with frequently updated content.. A web feed is a document (often XML-based) whose discrete content items include web links to the source of the content. News websites and blogs are common sources for web feeds, but feeds are also used to deliver structured information ranging from weather data to top-ten lists of hit tunes to search results. The two main web feed formats are RSS and Atom.

Feedburner is a free web feed management owned by Google that helps to host our feeds. If you have an account with Google you can publish your feeds through Feedburner.

Feedburner

Feedburner

We have to do the following three things to help users subscribe to our blog posts.

1. Create an RSS feed for our blog
2. Register our feed in Feedburner
3. Include the feed subscribe link in our blog

4.2.1 Create an RSS feed for our blog

RSS

RSS Rich Site Summary (originally RDF Site Summary, often dubbed Really Simple Syndication) is a family of web feed formats used to publish frequently updated works�such as blog entries, news headlines, audio, and video�in a standardized format.

RSS feeds can be read using software called an "RSS reader", "feed reader", or "aggregator", which can be web-based, desktop-based, or mobile-device-based. The user subscribes to a feed by entering into the reader the feed's URI or by clicking a feed icon in a web browser that initiates the subscription process. The RSS reader checks the user's subscribed feeds regularly for new work, downloads any updates that it finds, and provides a user interface to monitor and read the feeds. RSS allows users to avoid manually inspecting all of the websites they are interested in, and instead subscribe to websites such that all new content is automatically checked for and advertised by their browsers as soon as it is available.

For this task we have to create a new action in the BlogController called Feed.

public ActionResult Feed()
{
	// TODO: Create an RSS feed and write into the response
}

Listing 15. Feed action in BlogController

.NET simplifies the work of creating web feeds (RSS, Atom) through a set of classes available in the System.ServiceModel.Syndication namespace. To use those classes we have to add reference to the System.ServiceModel assembly.

To create a feed we have to do the following things inside the action.

a. Create a collection of SyndicationItem objects from the latest posts.
b. Create an instance of SyndicationFeed class passing the SyndicationItem collection.
c. Format syndication feed in the format (RSS, Atom) through corresponding feed formatter.
d. Write the feed to the response.

For RSS 2.0 format, we have to use the Rss20FeedFormatter class.

The complete code of the action is shown below. It's not that complicated to understand. I stole this code from some blog long back (I forgot that link) and so the credit goes to him!

using System;
using System.Configuration;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Text;
using System.Web;
using System.Web.Mvc;
using JustBlog.Core;
using JustBlog.Core.Objects;
using JustBlog.Models;

namespace JustBlog.Controllers
{
    ...

    public ActionResult Feed()
    {
	    var blogTitle = ConfigurationManager.AppSettings["BlogTitle"];
	    var blogDescription = ConfigurationManager.AppSettings["BlogDescription"];
	    var blogUrl = ConfigurationManager.AppSettings["BlogUrl"];

	    // Create a collection of SyndicationItemobjects from the latest posts
	    var posts = _blogRepository.Posts(0, 25).Select
	    (
	      p => new SyndicationItem
		      (
			      p.Title,
			      p.Description,
			      new Uri(string.Concat(blogUrl, p.Href(Url)))
		      )
	    );

	    // Create an instance of SyndicationFeed class passing the SyndicationItem collection
	    var feed = new SyndicationFeed(blogTitle, blogDescription, new Uri(blogUrl), posts)
	    {
		    Copyright = new TextSyndicationContent(String.Format("Copyright � {0}", blogTitle)),
		    Language = "en-US"
	    };

	    // Format feed in RSS format through Rss20FeedFormatter formatter
	    var feedFormatter = new Rss20FeedFormatter(feed);

	    // Call the custom action that write the feed to the response
	    return new FeedResult(feedFormatter);
    }
}

Listing 16. Feed action

The web.config entries are:

<add key="BlogTitle" value="JustBlog" />
<add key="BlogDescription" value="A Technical blog, where I write interesting things about many subjects." />
<add key="BlogUrl" value="http://www.justblog.com" />

Listing 17. web.config

Let's see about the custom action result class called FeedResult which writes the feed information to the response.

FeedResult class

We can directly write the feed to the response inside the action using an XmlWriter. Instead we've created an a custom action result to wrap that logic for the following reasons.

1. Since we avoid the dependency with response object our action is easy to test.
2. Reusability. Tomorrow if you need to create different feeds you can easily reuse the logic.

Create a new class called FeedResult in the root of the web project and copy-paste the below code. What we are doing in the code is: creating an instance of XmlWriter and writing the contents of feed from the RSS formatter to the response. Note, the content type of the response is "application/rss+xml", which stands for RSS feed.

using System;
using System.ServiceModel.Syndication;
using System.Text;
using System.Web.Mvc;
using System.Xml;

namespace JustBlog
{
    public class FeedResult : ActionResult
    {
        public Encoding ContentEncoding { get; set; }
        public string ContentType { get; set; }

        private readonly SyndicationFeedFormatter _feed;
        public SyndicationFeedFormatter Feed
        {
          get { return _feed; }
        }

        public FeedResult(SyndicationFeedFormatter feed)
        {
          _feed = feed;
        }

        public override void ExecuteResult(ControllerContext context)
        {
          if (context == null)
            throw new ArgumentNullException("context");

          var response = context.HttpContext.Response;
          response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/rss+xml";

          if (ContentEncoding != null)
            response.ContentEncoding = ContentEncoding;

          if (_feed != null)
            using (var xmlWriter = new XmlTextWriter(response.Output))
            {
              xmlWriter.Formatting = Formatting.Indented;
              _feed.WriteTo(xmlWriter);
            }
        }
    }
}

Listing 18. FeedResult class

To check whether your feed is working fine just browse to the url, http://localhost:<port>/feed. Firefox formats the feed and displays the content user friendly as below. It even shows you a subscribe button at the top with list of reader options like Live Bookmarks, Microsoft Outlook etc.

Viewing feed

Viewing feed

Google displays the feed as raw xml.

Viewing feed in Chrome

Viewing feed in Chrome

Our RSS feed is ready. What's next? host in Feedburner!

4.2.2 Register our feed in Feedburner

Registering our feed to Feedburner is simple. All we have to do is enter our feed url for ex. http://www.justblog.com/feed into the "Burn your feed at this instant" textbox.

Adding feed in Feedburner

Adding feed in Feedburner

I can't demonstrate what happens next. Feedburner will check whether the url is valid and if yes you'll get finally a link through which anyone can access your feed. The link would be something like http://feeds.feedburner.com/justblog. Now, all we have to do is create a link in our blog and point that to this url.

This is a very simple task! Update the _Layout.cshml to include an anchor element with "href" pointing to our feed.

...

<div id="site_content">

  <div class="feeddiv">
	<a href="http://feeds.feedburner.com/justblog" title="Subscribe to my blog" >Subscribe to my blog</a>
  </div>

  @Html.Partial("_Search")
  @RenderBody()
  @Html.Action("Sidebars")
</div>

...

Listing 19. _Layout.cshml

Note that, I've to create a new CSS class called "feeddiv" and adjusted the "#search-form" in style.css file to display things properly.

Subscribe link

Subscribe link

So.. we have successfully integrated both AddThis and FeedBurner for sharing and subscribtions. Let's work on the next story that deals about scripts/css bundling and minification, which is quite interesting!

5. Story #3 - Scripts, CSS bundling and minification

5.1 Why bundling and minification?

The performance of a web page is affected by the amount of script and CSS files referenced in the page and the size of them. Bundling and minification is a common practice used to increase the performance of a page. Bundling decreases the number of server requests by combining a bunch of Javascript or CSS files into one. Minification reduces the time taken to download a file by compressing it. Together they play a great role in saving the performance of a page.

The bundling and minification process is taken care by the assembly called System.Web.Optimization which was first shipped with ASP.NET MVC 4. Now it's available as a separate package called Microsoft.AspNet.Web.Optimization that can be downloaded through nuget.

To install the package, open the Package Manager Console and run the below command,

Install-Package Microsoft.AspNet.Web.Optimization

On sucess installation, you will see two assemblies: System.Web.Optimization and WebGrease added to the JustBlog project. Unlike before, the bundling framework depends upon this new assembly WebGrease which provides a suite of tools for optimizing javascript, css files and images. You can learn more about it from this page.

I won't go deep into the bundling and minification feature but I'll discuss about some important points. There is an excellent article written by Rick Anderson about this subject right here. I highly recommend it (you can read later ;).

Let me tell you some important things that you should know to complete this user story.

5.1.1 Bundle

A bundle is a collection of Scripts/CSS files that is represented by the class called Bundle. There are two types of bundles available: StyleBundle and ScriptBundle, both derived from the class Bundle.

As the names, StyleBundle is used to create a bundle from CSS files and ScriptBundle is used to create a bundle from JavaScript files. You cannot mix both JavaScript and CSS in one bundle (that makes sense right?).

5.1.1.1 How to create a bundle?

Bundles are created at the application start event. All the created bundles can be accessed through the Bundles property (which is of type BundleCollection) in the BundleTable static class.

The following statements shows how you can create a script bundle comprising two javascript files.

var jsBundle = new ScriptBundle("~/app/js");
jsBundle.Include("~/Scripts/jquery.js");
jsBundle.Include("~/Scripts/custom.js");

Listing 20. Example to create script bundle

The parameter we passed to the constructor is the virtual path of the bundle. It can also be perceived as the name of the bundle. From any view, you can easily load a bundle by specifying it's virtual path (i.e. the name) as shown in the below listing. But, you should avoid loading bundles like below. There are built-in helper classes available in the framework that not only simplify loading bundles but also takes care of versioning for bundles.

Ex.

<script type="text/javascript" src="~/app/js"></script>

Listing 21. Referring bundle in view (without using helper class)

The methods provided by the Bundle class supports chaining. You can reduce the code shown in listing 21. into a single line as below.

var jsBundle = new ScriptBundle("~/app/js").Include("~/Scripts/jquery.js").Include("~/Scripts/custom.js");

Listing 22. Creating a script bundle with chaining methods

Likewise, we can create a bundle for CSS files using the StyleBundle class.

5.1.1.2 Bundle constructor overloads

The Bundle class has some interesting overloads.

5.1.1.2.1 Bundle(string virtualPath, params IBundleTransform[] transforms)

IBundleTransform is the interface that controls the way in which the bundle is minified and transformed. JsMinify and CssMinify are the built-in classes that implements IBundleTranform which controls the way in which the JavaScript and CSS files are bundled as default. We can customize the bundling/minifying process by passing our own custom implementations to the Bundle class using this constructor.

Ex.

var customJsBundle = new Bundle("~/app/js", new CustomJsTransform());

Listing 23. Passing custom IBundleTransform implementation to Bundle's constructor

5.1.1.2.2 Bundle(string virtualPath, string cdnPath)

Usually developers prefer to load popular JavaScript frameworks like jQuery, jQuery UI through CDN to save bandwidth. To create bundles for CDN files we can use this constructor.

Ex.

var jqueryBundle = new ScriptBundle("~/jquery", "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js")
						.Include("~/Scripts/jquery-{version}.js");

Listing 24. Creating a script bundle with CDN file

In the above statement, I've also included the local jquery file. In the debug mode, the local jquery file will be loaded as it is without bundling and minification. In the release mode, if the UseCdn property of the BundleCollection class (where all the bundles are added) is set to true then the CDN file will be loaded (without any compression) else the local file will be compressed and served.

{version} wild card

The {version} wild card matching shown above is used to automatically create a jQuery bundle with the appropriate version of jQuery in your Scripts folder.

bundles.Add(new ScriptBundle("~/bundles/jquery").Include("~/Scripts/jquery-{version}.js"));

In the above example, using a wild card provides the following benefits:

  • Allows you to use NuGet to update to a newer jQuery version without changing the preceding bundling code or jQuery references in your view pages.
  • Automatically selects the full version for debug configurations and the ".min" version for release builds.

In our project, we have to create bundles for three pages: Layout, Login and Manage. The following are the tasks we have to do to complete this user story.

1. Create the necessary bundles for Layout
2. Create the necessary bundles for Login page
3. Create the necessary bundles for Manage page

5.1.2 Create the necessary bundles for Layout

We have to create all the script and CSS bundles needed by the application at the start event. Instead of coding inside the OnApplicationStarted method of Global.asax.cs, let's place them in a separate class.

Create a new class called BundleConfig under App_Start folder (if there is no such class exists already). Create a new static method called RegisterBundles that takes a parameter of type BundleCollection.

using System.Web.Optimization;

namespace JustBlog
{
    public class BundleConfig
    {
	    public static void RegisterBundles(BundleCollection bundles)
	    {
		    // TODO: create all the bundles
	    }
    }
}

Listing 25. BundleConfig class

As of now, this is how the script/css references looks in the _Layout.cshtml.

<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/simple/style.css")" />
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.10.0/jquery.validate.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.min.js"></script>
<script src="@Url.Content("~/Scripts/tagcloud.js")"></script>
<script src="@Url.Content("~/Scripts/app.js")"></script>

Listing 26. CSS/script references in _Layout.cshtml

We are not using the tagcloud.js file so we can ignore that. For the layout page, we have to create one CSS bundle that contains one css file (style.css) and four javascript bundles each containing single javascript file. You should ask a question at this point. Why we need four bundles and why we can't use a single bundle to combine all the four JavaScript files?

The answer is, if we bundle the CDN files with others (either from CDN or local), then you are losing the advantage of using the CDN itself. The main advantage of loading files from CDN is, the browser may already have cached copy of those files. If you bundle a CDN file with other then it's more like serving a local file from your server, so don't do that! You can happily bundle all your local javascript files into one but not CDN files.

Let's fill the BundleConfig class with our bundles.

public static void RegisterBundles(BundleCollection bundles)
{
	// Use the CDN file for bundles if specified.
	bundles.UseCdn = true;

	// jquery library bundle
	var jqueryBundle = new ScriptBundle("~/jquery", "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js")
							.Include("~/Scripts/jquery-{version}.js");
	bundles.Add(jqueryBundle);

	// jquery validation library bundle
	var jqueryValBundle = new ScriptBundle("~/jqueryval", "http://ajax.aspnetcdn.com/ajax/jquery.validate/1.10.0/jquery.validate.min.js")
								.Include("~/Scripts/jquery.validate.js");
	bundles.Add(jqueryValBundle );

	// jquery unobtrusive validation library
	var jqueryUnobtrusiveValBundle = new ScriptBundle("~/jqueryunobtrusiveval", "http://ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.min.js")
										.Include("~/Scripts/jquery.validate.unobtrusive.js");
	bundles.Add(jqueryUnobtrusiveValBundle);

	// application script bundle
	var layoutJsBundle = new ScriptBundle("~/js").Include("~/Scripts/app.js");
	bundles.Add(layoutJsBundle );

	// css bundle
	var layoutCssBundle = new StyleBundle("~/css").Include("~/Content/themes/simple/style.css");
	bundles.Add(layoutCssBundle );

	// TODO: bundles for other pages
}

Listing 27. Creating bundles for Layout

Even-though we are loading the files from CDN we should have a local copy for the un-minified version of each file at the server to make the application run smoothly in the debug mode. As I already told, in the case of debug mode the files are loaded from the server.

Bundling in debug/release mode

When you have a CDN bundle, in the debug mode, the local file (js or CSS) is being served as it is, without bundled and minified (it's more like directly referencing the files in pages). Note that, the local file should be an un-minified version because as default bundling ignores the ".min" files in debug mode. In release mode, the CDN file will be used if the UseCdn property of the BundleCollection (where all the bundles are added) is set to true. Else, the local file will be served after minification.

When you have both ".min" and non ".min" version of a js/CSS file in a folder, in debug mode, the un-minifed version will be used and in the release mode the ".min" file is used as it is without further compression.

Let's update the Global.asax.cs to call the RegisterBundles method from the overridden OnApplicationStarted method.

protected override void OnApplicationStarted()
{
	BundleConfig.RegisterBundles(BundleTable.Bundles);

	//...other start-up code
}

Listing 28. Global.asax.cs

Next thing we have to do is, remove the references from the _Layout.cshtml file and call the bundles through the built-in helper classes.

5.1.2.1 @Styles and @Scripts

System.Web.Optimization assembly provides two helper classes to load bundles in views. Styles class is used to load CSS bundles and Scripts class is used to load JavaScript bundles through the Render method.

To use these classes directly in the views without specifying their namespace we should add the System.Web.Optimization namespace to the web.config file in the Views folder.

<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
	<pages pageBaseType="System.Web.Mvc.WebViewPage">
		<namespaces>
			<add namespace="System.Web.Mvc" />
			<add namespace="System.Web.Mvc.Ajax" />
			<add namespace="System.Web.Mvc.Html" />
			<add namespace="System.Web.Routing" />
			<add namespace="System.Web.Optimization" />
			<add namespace="JustBlog"/>
		</namespaces>
	</pages>
</system.web.webPages.razor>

Listing 29. web.config (under Views folder)

Below is the updated _Layout.cshtml page.

@Styles.Render("~/css")
@Scripts.Render("~/jquery", "~/jqueryval", "~/jqueryunobtrusiveval", "~/js")

Listing 30. Loading bundles using helper class

The Render method takes params string[] as parameter. So we can load multiple bundles through a single method by passing their virtual paths as shown in the second statement.

5.1.2.2 Test Drive

Before running the application make sure you have local copies for the un-minified versions of JavaScript libraries (jQuery, jQuery validation..) in the Scripts folder and also set the debug mode to "true" in web.config.

<compilation debug="true" targetFramework="4.5" />

Listing 31. Compilation mode in web.config

If you have done everything correctly then you will see the below screen.

Bundling with debug mode

Bundling with debug mode

Now, let's change the debug mode to "false" in web.config and run the application.

Bundling with release mode

Bundling with release mode

Oops! the images are not loaded properly. Clearly the "images" folder is not in the root of our project and then why the browser is requesting for the wrong place. To find out go to the "Net" tab in the Firebug panel.

Firebug Net tab

Firebug Net tab

The virtual path of the CSS bundle we have specified is "~/css" (Listing 27). Because of that, the relative image paths we have specified in the CSS file is not working correctly. There are different ways we can solve this problem. One way is to create a custom IBundleTransform and pass that to the CSS bundle that will take care of fixing the image reference issue as specified here. Another easy way is create the virtual path of the CSS bundle similar like the actual path i.e.

var layoutCssBundle = new StyleBundle("~/Content/themes/simple/css")
						.Include("~/Content/themes/simple/style.css");

Listing 32. CSS bundle for layout page (after the fix)

Let's go with the second approach. Don't forget to update the _Layout.cshtml as well.

@Styles.Render("~/Content/themes/simple/css")

Listing 33. _Layout.cshtml

After the above changes, if you run the application, everything should be cool! Let's create the bundles for the other two pages.

5.1.3 Create the necessary bundles for Login page

We have referenced a CSS and three JavaScript files from CDN in our login page.

<link href="@Url.Content("~/Content/themes/simple/admin.css")" rel="stylesheet" />
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.10.0/jquery.validate.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.min.js"></script>

Listing 34. Login.cshtml

We have to create a new bundle for the admin.css file. Regarding the JavaScript files we already have the bundles created for them.

Below is the CSS bundle statement that we have to add in the BundleConfig class.

var loginCssBundle = new StyleBundle("~/Content/themes/simple/admin").Include("~/Content/themes/simple/admin.css");
bundles.Add(loginCssBundle);

Listing 35. Bundles for Login page

Next, we have to replace the references with bundles in the Login.cshtml page.

@Styles.Render("~/Content/themes/simple/admin")
@Scripts.Render("~/jquery", "~/jqueryval", "~/jqueryunobtrusiveval")

Listing 36. Loading bundles in Login.cshtml

That's it. Let's complete the same work for the Manage page.

5.1.4 Create the necessary bundles for Manage page

We have used lot of libraries in the Manage page and we got lot of references out there as shown below.

<link href="@Url.Content("~/Scripts/jquery.jqGrid-4.4.2/css/ui.jqgrid.css")" rel="stylesheet" />
<link href="@Url.Content("~/Content/themes/simple/jquery-ui-1.9.2.custom/css/sunny/jquery-ui-1.9.2.custom.min.css")" rel="stylesheet" />
<link href="@Url.Content("~/Content/themes/simple/admin.css")" rel="stylesheet" />
<script src="http://code.jquery.com/jquery-1.8.2.js"></script>
<script src="http://code.jquery.com/ui/1.9.1/jquery-ui.js"></script>
<script src="@Url.Content("~/Scripts/jquery.jqGrid-4.4.2/js/jquery.jqGrid.min.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.jqGrid-4.4.2/js/i18n/grid.locale-en.js")"></script>
<script src="@Url.Content("~/Scripts/tiny_mce/tiny_mce.js")"></script>
<script src="@Url.Content("~/Scripts/admin.js")"></script>

Listing 37. Manage.cshtml

Let's create the necessary bundles for them. In the Manage view, additionally, we have also used the jQuery UI library. So we have to create a bundle for that as well.

// jQuery UI library bundle
var jqueryUIBundle = new ScriptBundle("~/jqueryui", "http://ajax.aspnetcdn.com/ajax/jquery.ui/1.9.1/jquery-ui.min.js").Include("~/Scripts/jquery-ui.js");
bundles.Add(jqueryUIBundle );

// CSS bundle
var manageCssBundle = new StyleBundle("~/Scripts/jqgrid/css/bundle").Include("~/Scripts/jqgrid/css/ui.jqgrid.css");
bundles.Add(manageCssBundle);

// jQuery UI library CSS bundle
var jqueryUICssBundle =
new StyleBundle("~/Content/themes/simple/jqueryuicustom/css/sunny/bundle").Include("~/Content/themes/simple/jqueryuicustom/css/sunny/jquery-ui-1.9.2.custom.css")
bundles.Add(jqueryUICssBundle);

// tinyMCE library bundle
var tinyMceBundle = new ScriptBundle("~/Scripts/tiny_mce/js").Include("~/Scripts/tiny_mce/tiny_mce.js");
bundles.Add(tinyMceBundle);

// Other scripts
var manageJsBundle = new ScriptBundle("~/manage/js").Include("~/Scripts/jqgrid/js/jquery.jqGrid.js").Include("~/Scripts/jqgrid/js/i18n/grid.locale-en.js").Include("~/Scripts/admin.js");
bundles.Add(manageJsBundle);

Listing 38. Creating bundles for Manage page

There are three things I should tell you.

The first thing is, I've changed the names for couple of folders. I've changed the folder name from jquery-ui-1.9.2.custom (which is under /Content/themes/simple) to jqueryuicustom and jquery.jqGrid-4.4.2 (under Scripts) to jqGrid. The reason why we have to do this is, the virtual path we pass to the Bundle's constructor should not contain special characters like ".".

The second thing is even for the tinyMCE script bundle we have passed virtual path that resembles the folder structure. Normally we have to do this for CSS bundles to avoid image reference issue. The tinyMCE library loads other JS files from the same location. So if we pass any name other than the actual folder path things won't go well.

The third thing is, I've to change the jquery.jqGrid.src.js (which is under Scripts/jqgrid/js) to jquery.jqGrid.js because at debug mode the non-minified version is loaded and unfortunately the name of the file is not correct.

Finally, replace the references in the Manage.cshtml page,

@Styles.Render("~/Content/themes/simple/jqueryuicustom/css/sunny/bundle", "~/Scripts/jqgrid/css/bundle", "~/Content/themes/simple/admin")
@Scripts.Render("~/jquery", "~/jqueryui", "~/Scripts/tiny_mce/js", "~/manage/js")

Listing 39. Manage.cshtml

If you have done everything right you should see the below screen without any errors at both debug and release modes.

Admin page with bundling

Admin page with bundling

We've successfully completed the bundling and minification for the assets (CSS, scripts) to improve the performance for all the pages. Let's work on handling exceptions.

6. Story #4 - Implement exception handling using ELMAH

I wrote an article many months back regarding handling exceptions effectively in an ASP.NET MVC application. I recommend to read that article when you are free. In this part, we are not going to delve deeply into that subject but I highlight some important points.

6.1 Exception filters and HandleErrorAttribute

ASP.NET MVC simplifies handling exceptions through exception filters. Exception filters are one type of filters (there are other types like Authorization, Action etc.) which get invoked whenever some exception fires in an action.

All the exception filters implements the interface IExceptionFilter. ASP.NET MVC provides a built-in class that implements IExceptionFilter called HandleErrorAttribute which does a pretty decent job. The HandleErrorAttribute filter returns an error view (which is located in the Shared folder) whenever some exception happens.

Exception filters are not a complete answer for exception handling. One of the main disadvantage is, it can't handles all the exceptions raised by the application. It can catch only the exceptions that are raised inside the controller context. Because of that we can't rely completely on filters. Either we have to use a framework like ELMAH or rely on the Application_Error event.

As default, when you create an empty ASP.NET MVC project, the HandleError filter is registered into the application, which you can notice down from the FilterConfig.cs file under App_Start folder. Since we are not going to use any filters you can delete that file happily.

6.2 ELMAH

Other than the catch-all exception stuff there is one more thing these exception filters misses completely, logging! Logging exceptions is trivial even in simple applications and there are many frameworks like EnterpriseLibrary logging block, log4net etc. that helps from simple to advanced logging.

ELMAH is an error handling and logging library that is easily pluggable to an ASP.NET application. Unlike other frameworks, the important advantage of this framework is it is very easy to setup. At a simple level to configure ELMAH all you have to do is drop the assembly and add some configuration to web.config. ELMAH catches all the unhandled exceptions raised by the application and logs them to an xml file or database depending upon the configuration.

Some of the important features provided by this library are (from the ELMAH website):

  • Logging of nearly all unhandled exceptions.
  • A web page to remotely view the entire log of recorded exceptions.
  • A web page to remotely view the full details of any one logged exception, including colored stack traces.
  • In many cases, you can review the original yellow screen of death that ASP.NET generated for a given exception, even with customErrorsmode turned off.
  • An e-mail notification of each error at the time it occurs.
  • An RSS feed of the last 15 errors from the log.
ELMAH error page

ELMAH error page

Why ELMAH is different from log4net?

Elmah serves a purpose of tracking errors and exceptions for your web applications and allows you to easily log or view those exceptions via many different mechanisms (sql, rss, twitter, files, email, etc). If you have no built in exception handling Elmah will most likely get you want you are looking for in terms of exception handling in a web application environment.

Log4net can be used for exception logging as well, however you might need to roll your own handlers to plug into your web application. Log4net will shine over Elmah if you need to do other types of information logging as log4net is a general purpose logging framework. Log4net can also be used in almost any .net application.

The following are the tasks we've to do to complete this user story.

1. Install and configure ELMAH
2. Return custom error page for 404 and other errors

6.3 Install and configure ELMAH

6.3.1 Install ELMAH

We can install ELMAH through Nuget. Open the Package Manager Console from Tools. Make sure the project dropdown set to JustBlog and run the below command.

Install-Package elmah

If the package is installed successfully, you'll see the assembly ELMAH added to the project with the following entries in web.config.

<configSections>
	<sectionGroup name="elmah">
		<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
		<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
		<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
		<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
	</sectionGroup>
</configSections>

<system.web>
	<httpModules>
		<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
		<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
		<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
	</httpModules>
</system.web>

<system.webServer>
	<validation validateIntegratedModeConfiguration="false" />
	<modules>
		<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
		<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
		<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
	</modules>
</system.webServer>

<elmah>
	<security allowRemoteAccess="false" />
</elmah>

<location path="elmah.axd" inheritInChildApplications="false">
	<system.web>
		<httpHandlers>
			<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
		</httpHandlers>
	<authorization>
		<allow roles="admin" />
		<deny users="*" />
	</authorization>
	</system.web>
	<system.webServer>
		<handlers>
			<add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
		</handlers>
	</system.webServer>
</location>

Listing 40. ELMAH configuration sections in web.config

A text file with name Elmah.txt is also added to the folder App_Readme. I would recommend you to go through the text file which contains instructions about accessing ELMAH error log from the application and securing them from unauthorized access. The errors generated in the application is displayed in the elmah.axd resource generated by ELMAH handler. If we don't set proper authorization to elmah.axd, anyone can access the error logs and that may create serious security issues. If we explore the <location> element that points to elmah.axd in the above configuration, it has an authorization element that controls it's accessibility.

We got to do couple of changes in the above configuration. First, replace the roles="admin" with users="admin" in the <allow> element as shown below.

<authorization>
	<allow users="admin" />
	<deny users="*" />
 </authorization>

Listing 41. Authorizing elmah.axd using <authorization> element

This makes sure only the admin can access the error logs and not anyone else.

<elmah>
	<security allowRemoteAccess="true" />
</elmah>

Listing 42. Enabling remote access through <security> element

Next, change the allowRemoteAccess property of the <security> element to true to allow admin to see the error log from remote machines. As default, the error logs are only accessed from the local server.

Enabling or Disabling Remote Access

ELMAH provides a configuration section and a setting to enable or disable remote access to the error log display and feeds. When disabled (the default), only local access to the error log display and feeds is allowed. The snippet below shows how to enable remote access:

	<elmah>
		<security allowRemoteAccess="1" />
	</elmah>

Remote access is enabled when the value of the allowRemoteAccess attribute is either 1, yes, true or on. Otherwise it is disabled. Local access is always available.

6.3.2 More about ELMAH

At a technical point, ELMAH is a http module that listens to the application error event. Whenever the error event fires, ELMAH logs the error details to the configured data source. ELMAH also uses http handlers for sending emails, generating HTML/RSS markup, generating tweets from the error log. To know very deep understanding about the ELMAH and it's architecture please read this article.

6.3.3 Configure ELMAH

We have to configure three things in ELMAH. First, we want the application errors to be logged in database and so we have to configure the database settings. Next, whenever an exception occurs, we want an email to be send a configured account and so we have to specify the email settings. As default ELMAH handles all the errors and logs them. We don't want the 404 errors being handled and so we have to specify the filter settings.

6.3.4 Configure database

As default ELMAH stores all the errors in the in-memory storage, which will get cleared whenever the application recycles. ELMAH also provides implementations to log errors to different data-sources like,

  • Microsoft Access
  • Oracle database
  • Microsoft SQL Server database
  • SQLite database file
  • VistaDB (Express Edition) database file
  • XML files

We are going to use SQL Server database to log the errors. The error log configuration is controlled by the <errorLog> section. We are going to use the same database JustBlog to log the errors. ELMAH provides an SQL script which we can download from here that has to be executed in the JustBlog database to create the required tables and stored procedures.

Once you run the script you should see a table called ELMAH_Error and three stored procedures created in the database. Don't worry about the table and SPs, they are used by ELMAH directly. Once you done with the database stuff, you have to add the following section under <elmah> in web.config.

<errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="JustBlogDbConnString" />

Listing 43. <errorLog> element

The above section tells ELMAH to use the SQL server database as the error log source with the connection string specified in the JustBlogDbConnString. To know more about configuring <errorLog> for other data sources, visit this page.

6.3.5 Configure email

ELMAH also provide an option to send email to a configured account whenever an exception happens. This is one of the nice feature in ELMAH and it is controlled by the <errorMail> element.

<errorMail from="admin@justblog.com"
               to="admin@justblog.com"
               subject="Exception occured in JustBlog"
               priority="High"
               async="true"
               smtpServer="someserver"
               useSsl="false"
               noYsod="true" />

Listing 44. <errorMail> element

You should add the above section under <elmah>. Don't forget to change the from, to and smtpServer properties.

6.3.6 Setting error filters

ELMAH handles all the errors raised by the application. We don't want the 404 errors being logged because most of the times we are not interested in that. Through the <errorFilter> element we can easily specify the errors that needs to be filtered out based upon the status code, exception type or others.

The following is the configuration we have to add under <elmah> to filter 404 errors. To know more about error filtering please refer this page.

<errorFilter>
	<test>
		<jscript>
			<expression>
				<![CDATA[
				// @assembly mscorlib
				// @assembly System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
				// @import System.IO
				// @import System.Web

				HttpStatusCode == 404
				|| BaseException instanceof FileNotFoundException
				|| BaseException instanceof HttpRequestValidationException
				/* Using RegExp below (see http://msdn.microsoft.com/en-us/library/h6e2eb7w.aspx) */
				|| Context.Request.UserAgent.match(/crawler/i)
				|| Context.Request.ServerVariables['REMOTE_ADDR'] == '127.0.0.1' // IPv4 only
				]]>
			</expression>
		</jscript>
	</test>
</errorFilter>

Listing 45. <errorFilter> element

That's all the configuration we have to do to. Let's do a simple test to make sure the errors are getting logged to database. I don't have proper email settings to send error emails and so I've commented out the <errorMail> configuration. Note that, ELMAH don't throw any error if some exception happens at logging or sending email but it trap it's own errors to some extent as said here.

6.3.7 Test Drive

Create an action in the BlogController that just throws not-implemented exception.

public ActionResult BadAction()
{
	throw new Exception("You forgot to implement this action!");
}

Listing 46. Test action that throws exception

If you browse to the url http://localhost:<port>/badaction you'll see the below error page.

Internal server error

Internal server error

We'll see in the next task about hiding the actual error to the user and displaying custom error page. For now, to see the error log, you've to login to the application and browse to http://localhost:<port>/elmah.axd. All the errors caught by ELMAH are displayed in a grid as shown below.

Elmah error log - elmah.axd

Elmah error log - elmah.axd

The above page is dynamically generated by the http handler available in ELMAH. By clicking the "Details..." link you'll see the complete description about the origination of the error and other details which is very helpful to debug the actual cause of the error.

Detail report of exception

Detail report of exception

We have pretty much completed the exception handling part. Still we got to finish one thing i.e. displaying custom error page instead of showing the yellow screen to the user.

6.4 Return custom error page for 404 and other errors

Usually in applications, instead of showing the actual error page we show custom error pages for different errors. In ASP.NET applications, custom error pages are returned through the <customErrors> section in web.config. For example, the below configuration section returns two different error pages for different errors based upon their status code.

<customErrors mode="On" redirectMode="ResponseRewrite">
	<error statusCode="404" redirect="NotFound.html"/>
	<error statusCode="500" redirect="ServerError.html"/>
</customErrors>

Listing 47. Returning custom error pages through <customErrors> section

In our case instead of returning ".html" pages we have to return MVC views from the actions. We could still use the <customErrors> section to return views but then we can't do "ResponseRewrite" (redirectMode) but only "ResponseRedirect."

In the case of "ResponseWrite", when some exception happens, the url of the browser is not changed and the custom error page is directly written to the response. In the case of "ResponseRedirect" the user will be redirected to the error page and hence the url will be changed. I personally don't recommend the second approach because of two reasons: first we are making an extra request just to let user to see the error page and second we are losing the error context.

So our plan is, when some exception happens, we have to return an error view by invoking an action from a controller without doing redirect.

To make this happen we've to write some code in the Application_Error event. Before that, let's create a controller with name ErrorController with couple of actions: one action returns view for 404 errors and the other one returns view for the remaining errors.

using System.Web.Mvc;

namespace JustBlog.Controllers
{
    public class ErrorController : Controller
    {
	    public ViewResult Index()
	    {
		    return View();
	    }

	    public ViewResult NotFound()
	    {
		    return View();
	    }
    }
}

Listing 48. ErrorController

To create the error views, right-click inside the actions and select "Create View". The contents of the views are just plain and simple, you can customize to your needs.

6.4.1 Index.cshtml

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Server error</title>
</head>
<body>
    <div>
        Sorry, some server error has occured.
    </div>
</body>
</html>

Listing 49. Index.cshtml

6.4.2 NotFound.cshtml

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Resource not found</title>
</head>
<body>
    <div>
        Sorry, the resource you are looking for not found.
    </div>
</body>
</html>

Listing 50. NotFound.cshtml

From the Application_Error event, we are going to programmatically invoke the ErrorController and call the corresponding action based on the HTTP status code.

Here is the complete code.

protected void Application_Error(object sender, EventArgs e)
{
  var httpContext = ((MvcApplication)sender).Context;
  var ex = Server.GetLastError();
  var status = ex is HttpException ? ((HttpException)ex).GetHttpCode() : 500;

  // Is Ajax request? return json
  if (httpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
  {
	httpContext.ClearError();
	httpContext.Response.Clear();
	httpContext.Response.StatusCode = status;
	httpContext.Response.TrySkipIisCustomErrors = true;
	httpContext.Response.ContentType = "application/json";
	httpContext.Response.Write("{ success: false, message: \"Error occured in server.\" }");
	httpContext.Response.End();
  }
  else
  {
	var currentController = " ";
	var currentAction = " ";
	var currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));

	if (currentRouteData != null)
	{
	  if (currentRouteData.Values["controller"] != null &&
		  !String.IsNullOrEmpty(currentRouteData.Values["controller"].ToString()))
	  {
		currentController = currentRouteData.Values["controller"].ToString();
	  }

	  if (currentRouteData.Values["action"] != null &&
		  !String.IsNullOrEmpty(currentRouteData.Values["action"].ToString()))
	  {
		currentAction = currentRouteData.Values["action"].ToString();
	  }
	}

	var controller = new ErrorController();
	var routeData = new RouteData();

	httpContext.ClearError();
	httpContext.Response.Clear();
	httpContext.Response.StatusCode = status;
	httpContext.Response.TrySkipIisCustomErrors = true;

	routeData.Values["controller"] = "Error";
	routeData.Values["action"] =  status == 404 ? "NotFound" : "Index";

	controller.ViewData.Model = new HandleErrorInfo(ex, currentController, currentAction);
	((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
  }
}

Listing 51. Application_Error event in Global.asax.cs

We have also done some special coding to handle AJAX requests. There is no point of returning a view in exceptions caused by AJAX requests and so in those cases we are returning a simple JSON object to the client. Now if you browse to the "badaction", you'll see the following error page. (Don't forget to remove the BadAction from the BlogController :)

Custom error page

Custom error page

Now our blog admin can sleep peacefully! He knows that, whenever some exception happens, the errors are captured and an email will be sent to him.

7. Story #5 - Implement Contact page

Like in any blog we need a "Contact" page; that let's anyone to post message to us. We also need an "AboutMe" page but I leave that work to you. The Contact page is very simple. All it has is a contact form. To keep the user story simple, when someone post a message, we'll just send an email to the admin and not going to record the message to the database.

First, we have to create a model to capture the message submitted by the user and next we have to create the necessary controller actions and views.

1. Create model class to capture contact information
2. Create necessary controller actions
3. Create necessary views

7.1 Create a model to capture contact information

Create a new class called Contact in the JustBlog.Core project under the Objects folder. The Contact class contains properties to store user's name, email address, message subject, body etc. as shown below.

namespace JustBlog.Core.Objects
{
    public class Contact
    {
	    public string Name { get; set; }
	    public string Email { get; set; }
	    public string Website { get; set; }
	    public string Subject { get; set; }
	    public string Body { get; set; }
    }
}

Listing 52. Contact model

Let's apply some validations to this class.

using System.ComponentModel.DataAnnotations;

namespace JustBlog.Core.Objects
{
    public class Contact
    {
	    [Required]
	    public string Name { get; set; }

	    [Required, EmailAddress]
	    public string Email { get; set; }

	    [Url]
	    public string Website { get; set; }

	    [Required]
	    public string Subject { get; set; }

	    [Required]
	    public string Body { get; set; }
    }
}

Listing 53. Contact model with validations applied

7.2 Create necessary controller actions

We need to create two actions. One action renders the contact view and the other to handle the form post.

The below action returns the contact view which we will going to work soon.

public ViewResult Contact()
{
	return View();
}

Listing 54. Action that returns contact view

This is the action that handles the form's post.

..
using System.Net.Mail;

namespace JustBlog.Controllers
{
    [HttpPost]
    public ViewResult Contact(Contact contact)
    {
	    if (ModelState.IsValid)
	    {
		    using (var client = new SmtpClient())
		    {
			    var adminEmail = ConfigurationManager.AppSettings["AdminEmail"];
			    var from = new MailAddress(adminEmail, "JustBlog Messenger");
			    var to = new MailAddress(adminEmail, "JustBlog Admin");

			    using (var message = new MailMessage(from, to))
			    {
				    message.Body = contact.Body;
				    message.IsBodyHtml = true;
				    message.BodyEncoding = Encoding.UTF8;

				    message.Subject = contact.Subject;
				    message.SubjectEncoding = Encoding.UTF8;

				    message.ReplyTo = new MailAddress(contact.Email);

				    client.Send(message);
			    }
		    }

		    return View("Thanks");
	    }

	    return View();
    }
}

Listing 55. Action that handles contact form POST

The implementation is quite simple! If the model is valid, we are sending an email to the admin using the SmtpClient class and finally returning a view called "Thanks" (which will contain a simple message). If there are validation errors, the same view is returned back to the user that displays the errors.

To send the email successfully, we have to configure the SMTP settings in web.config.

<system.web>
	...
</system.web>
<system.net>
	<mailSettings>
		<smtp>
			<network host="mailserver.net" userName="justbloguser" password="password" port="80" />
		</smtp>
	</mailSettings>
</system.net>

Listing 56. smtp settings in web.config

Note that, you have to change the userName and password in the above configuration. Our actions are ready let's finish the views.

7.3 Create necessary views

We have to create two views. One view is used to display the contact form and the other one is to display the success message to the user.

Create a view under the folder Views/Blog with name Contact.cshtml. Using the built-in html helpers we can easily create the form and the input fields as shown below.

@model JustBlog.Core.Objects.Contact

@{
  ViewBag.Title = "Contact Me";
}

<h1>Contact Me</h1>

<div id="content">
  @using (Html.BeginForm())
  {
    <div class="form_settings">
      <p>
        <span class="field">Name</span>
        @Html.TextBoxFor(m => m.Name)
        @Html.ValidationMessageFor(m => m.Name)
      </p>

      <p>
        <span class="field">Email</span>
        @Html.TextBoxFor(m => m.Email)
        @Html.ValidationMessageFor(m => m.Email)
      </p>

      <p>
        <span class="field">Website</span>
        @Html.TextBoxFor(m => m.Website)
        @Html.ValidationMessageFor(m => m.Website)
      </p>

      <p>
        <span class="field">Subject</span>
        @Html.TextBoxFor(m => m.Subject)
        @Html.ValidationMessageFor(m => m.Subject)
      </p>

      <p>
        <span class="field">Body</span>
        @Html.TextAreaFor(m => m.Body, new { rows = 10 })
        @Html.ValidationMessageFor(m => m.Body)
      </p>

      <p style="padding-top: 15px">
        <span> </span>
        <input class="submit" type="submit" name="contact_submitted" value="submit">
      </p>
    </div>
  }
</div>

Listing 57. Contact.cshtml

Create another view with name Thanks.cshtml in the same location. All it has a message to the user and a link to the home page.

@{
    ViewBag.Title = "Thanks";
}

<h2>Thanks</h2>

<p class="bold">
  Your message is successfully sent to admin. Thanks for contacting us.
</p>

@Html.ActionLink("back to home", "Posts")

Listing 58. Thanks.cshtml

To test things are working fine, run the application and visit the contact page. If you've done everything right, you should see the below screen.

Contact form

Contact form

We have completed all the user stories except the last one "SEO optimization". Let's complete that as well.

8. Story #6 - SEO optimization

SEO

Search engine optimization (SEO) is the process of affecting the visibility of a website or a web page in a search engine's "natural" or un-paid ("organic") search results - wikipedia

Because of the time limitation, I can't discuss much about SEO optimization here. There is an excellent article that discusses about the points that should be taken care to improve the search engine ranking for a website.

In this user story, we are going to leverage meta tags for search engine optimization. Some experts says that, "meta tags" no longer helps to boost site traffic (at-least in Google). From the information I read, it looks like, "meta tags" are not completely dead! It's not a bad idea to throw them in our pages if they helps to improve SEO.

What are metatags?

Metadata is data (information) about data.

The <meta> tag provides metadata about the HTML document. Metadata will not be displayed on the page, but will be machine parsable.

Meta elements are typically used to specify page description, keywords, author of the document, last modified, and other metadata. The metadata can be used by browsers (how to display content or reload page), search engines (keywords), or other web services - w3schools

The important types of meta tags are: meta description, meta keyword and meta author. The meta description tag is used by google to display them in the search engine results.

Ex.

<head>
<meta name="description" content="Free Web tutorials">
<meta name="keywords" content="HTML,CSS,XML,JavaScript">
<meta name="author" content="St�le Refsnes">
<meta charset="UTF-8">
</head>

Listing 59. Meta tags

Using the "robots" meta tag we can advise search engine not to index a page and avoid showing them in search results. We need to apply that in our admin pages (Login, Manage).

<meta name="robots" content="noindex, nofollow">

Listing 60. Robots meta tag

The following are the tasks we have to do in this user story.

1. Add "keywords", "description" meta tags to the blog list, post views
2. Add "robots" meta tag to the admin views

8.1 Add "keywords", "description" meta tags to the blog list, post views

Like we passed the "Title" of the page through ViewBag, we can also do the same for meta keywords and description.

First, add the following keys to <appSettings> in web.config,

<add key="MetaDescription" value="A technical blog where you can learn latest information about different web technologies." />
<add key="Author" value="Vijaya Anand"/>

Listing 61. web.config

Open the _Layout.cshtml and include the following statements after the <title> section.

@using System.Configuration

...

<head>
	<meta name="keywords" content="@(ViewBag.Keywords ?? ConfigurationManager.AppSettings["MetaKeywords"])" />
	<meta name="description" content="@(ViewBag.Description ?? ConfigurationManager.AppSettings["MetaDescription"])"/>
	<meta name="author" content="@ConfigurationManager.AppSettings["Author"]"/>
</head>

Listing 62. _Layout.cshtml

For the list view (List.cshtml), we are going to use the default meta keywords and description loaded from configuration. But in the case of post view (Post.cshtml) we have to dynamically set those values from the Post model. Update the Post.cshtml with the below statements.

@model JustBlog.Core.Objects.Post

@{
  ViewBag.Title = Model.Title;
  ViewBag.Keywords = string.Join(" ", Model.Tags.Select(t => t.Name).ToArray());
  ViewBag.Description = Model.Meta;
}

...

Listing 63. Post.cshtml

To set the meta keywords we have concatenated all the tag names of the post.

8.2 Add "robots" meta tag to the admin views.

This is very simple! All we have to do is include the below mata tag line in both the Login.cshtml and Manage.cshtml views under Admin folder.

<meta name="robots" content="noindex, nofollow">

Listing 64. Login.cshtml and Admin.cshtml

That's all. With this user story we have completed all the work that's planned for JustBlog.

9. Conclusion

It's a long journey isn't it? When I was thinking to write this multi-part series.. I had the dream to write an article that teaches ASP.NET MVC to build real-world applications in a more fun way. Due to time constraint, I left the fun part and some of the places.. the writing went dry :(. I hope my next article will change that! The complete code is available in GitHub. I welcome you guys to fork the source code and play with it.

Any questions / suggestions / appreciations? drop a comment please!

Download Source  Fork in Github

blog comments powered by Disqus