Twitter style paging with ASP.NET MVC and jQuery


 

I really like the simplicity of the AJAX paging at Twitter so I decided to use the same type of paging, including a similar more button, on the start page on this site. It actually surprised me how simple it was to build it, including fallback for visitors that doesn’t have javascript enabled (such as our dear friend Google), with ASP.NET MVC and a few lines of javascript with jQuery.

Basic controller logic

Being an advocate of progressive enhancement I started out by building a non-AJAX version of the feature. In the controller for the start page I let the default (Index) method have a nullable int parameter named entryCount which tells the method how many of the latest blog entries it should return for the view to display.

public class HomeController : Controller

{

private const int defaultEntryCount = 10;


public ActionResult Index(int? entryCount)

{

if (!entryCount.HasValue)
entryCount = defaultEntryCount;
//Retrieve the first page with a page size of entryCount
int totalItems;
IEnumerable<Entry> entries = GetLatestEntries(1, 
entryCount.Value,out totalItems);
if (entryCount < totalItems)
AddMoreUrlToViewData(entryCount.Value);
return View(entries);
}
private void AddMoreUrlToViewData(intentryCount)
{
ViewData["moreUrl"] = Url.Action("Index", "Home",
 new { entryCount = entryCount + defaultEntryCount });
}
}

The method begins by making sure that the entryCount variable has a value, setting it to a default value if the parameter is null. It then retrieves as many of the latest blog entries as entryCount specifies by calling the GetLatestEntries method. I’ve omitted the GetLatestEntries method as it’s implementation will vary depending on blogging platform. The GetLatestEntries method also has an out parameter, totalItems, which tells us the total number of blog entries. I’m not a big fan of using out parameters but that’s the way the framework that I used for my blog (EPiServer Community) works so I decided to follow that pattern for consistency. If you use some other type of blogging platform I would recommend making a field of the totalItems.

The method moves on to check if there are more blog entries than the  number that will be displayed, in other words if a link for showing more entries should be displayed. If so, it calls the AddMoreUrlToViewData which, you guessed it, adds a route URL for displaying more entries to the ViewData dictionary.

Finally the method returns a ViewResult with the list of blog entries as the model.

Creating the views

The Index view for the Home controller is very simple. It simply renders a partial view named EntryTeaserList, passing along the list of blog entries (Model) and the it’s ViewData dictionary.

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<IEnumerable<Entry>>" %>
<asp:ContentContentPlaceHolderID="PrimaryMainContent"runat="server">
<% Html.RenderPartial("EntryTeaserList", Model, ViewData); %>
</asp:Content>

The partial view EntryTeaserList offers a bit more excitement. It renders an ordered list and displays a teaser for each blog entry by rendering another partial view, EntryTeaser, inside a list item, passing in each individual blog entry as model to it. It also checks if the ViewData dictionary contains a URL for a more link. If it does it renders a link with that URL in the href attribute.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Entry>>" %>
<div id="entryTeaserList">
<ol>
<% foreach (Entry item in Model) { %>
<li class="entryTeaser">
<% Html.RenderPartial("EntryTeaser", item); %>
</li>
<% } %>
</ol>
<% if(ViewData["moreUrl"] != null) { %>
<a href='<%= ViewData["moreUrl"] %>' id="moreLink">More</a>
<% } %>
</div>

Finally I made the link look like a button with some CSS.

#moreLink {
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border: 1px solid #666666;
background:url('/styles/gfx/more-bg.gif')repeat-x;
width: 100%;
display: block;
text-align: center;
padding: 0.4em 0 0.4em 0;
font-weight: bold;
color:#9aa57c; 
}
#moreLink:hover {
background:url('/styles/gfx/more-bg.gif') 0 -64px; repeat-x;
border: 1px solid #888888;
text-decoration: none;
}

As you might have noticed I used CSS to round the buttons corners. This will only work for some browsers. In this particular case I though that that was OK, but in many other situations I would instead have used images or javascript. You might also have noticed that the button has the same background image when it’s hovered over as when it isn’t. The background image is however offset vertically so it appears that it’s actually another image. I did this to keep the number of HTTP requests required to load the page to a minimum.

Spicing things up with AJAX

With the controller and views set up as described above I was done with the non-AJAX functionality. This will work fine for visitors that doesn’t have javascript enabled or debugging purposes, but this kind of paging is pretty pointless if the page has to reload. After all the point is that when someone clicks the more button the experience shouldn’t be that another page is displayed but that the list, more or less instantly, just grows a bit.

To add the AJAX functionality I begun by modifying the controllers Index method.

public ActionResult Index(int? entryCount)
{
if (!entryCount.HasValue)
entryCount = defaultEntryCount;
int totalItems;
if(Request.IsAjaxRequest())
{
int page = entryCount.Value / defaultEntryCount;
//Retrieve the page specified by the page variable with a page size o defaultEntryCount
IEnumerable<Entry> pagedEntries = GetLatestEntries(page,
 defaultEntryCount, out totalItems);
if(entryCount < totalItems)
AddMoreUrlToViewData(entryCount.Value);
return View("EntryTeaserList", pagedEntries);
}
//Retrieve the first page with a page size of entryCount
IEnumerable<Entry> entries = GetLatestEntries(1, 
entryCount.Value, outtotalItems);
if (entryCount < totalItems)
AddMoreUrlToViewData(entryCount.Value);
return View(entries);
}

The added code checks if the current request is an AJAX request, with the IsAjaxRequest extension method that ships with MVC. IsAjaxRequest determines if the current request is an AJAX request by looking for and at the X-Requested-With request header. If such an header, or actually any request parameter with that name, is set to “XMLHttpRequest” the method will return true. As jQuery’s AJAX methods sets that header this method works great in this example.

Anyway, if the current request is an AJAX request we know that the visitors browser already displays a number of blog entry teasers and instead of returning the full number of entries specified by the entryCount parameter we should only return those that haven’t yet been sent to the visitors browser. So, we calculate what page (as if we where using traditional paging) is requested by dividing entryCount with the defaultEntryCount constant. Then we retrieve a list of the entries on that page with a page size of defaultEntryCount. That is we retrieve the defaultEntryCount number of entries with an offset of page*defaultEntryCount.

Finally, if there are more entries we set the moreUrl in the ViewData dictionary by calling the AddMoreUrlToViewData method and return a ViewResult. This time around however we don’t return the default view for the method. Instead we return the EntryTeaserList partial view. This way we don’t return more HTML than necessary but we are able to reuse an already existing view. We could of course have returned the result as JSON or XML instead but that would have forced us to write javascript for rendering the markup to display the result and thereby duplicating the same markup in two places.

The last thing I did was to add a few lines of javascript to intercept clicks on the more link.

$(function() {
addMoreLinkBehaviour();
});
function addMoreLinkBehaviour() {
$('#entryTeaserList #moreLink').live("click", function() {
$(this).html("<img src='/images/ajax-loader.gif' />");
$.get($(this).attr("href"),function(response) {
$('#entryTeaserList ol').append($("ol", response).html());
$('#entryTeaserList #moreLink').replaceWith($("#moreLink", response));
});
return false;
});
}

When the DOM is ready we add a function to the click event of the more link, and, since I’m using the live function, to any future objects matching that selector. When the link is clicked two things initially happen. First the link’s text is replaced with an image to give the visitor some visual feedback if the response of the AJAX request isn’t instantly returned. Then an AJAX request is made to the same URL as the link had in it’s href attribute. That is, there’s no special URL for the AJAX request. This works as the controller takes care of determining what type of request it is.

When the server has responded with the partial view, that is an ordered list and possibly a new more link the list items are appended to the existing ordered list and the more link is replaced with the new more link if it exists. This way the more link is automatically updated with a new URL in it’s href attribute and the loading image is replaced with the original text.

Conclusion

I personally find this solution pretty elegant. It requires quite few lines of code and almost no duplicate logic or markup at all. It also offers full fallback functionality for visitors without javascript. However, if I was really interested in offering the best possible experience to human visitors with javascript disabled I could also give each blog entry teaser an id with it’s number in the list and include a hash tag with entryCount + 1 – defaultEntryCount in the more link’s target URL so that they would automatically be scrolled to the first entry that was added to the list. In my case I deemed that to be overkill though.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

S3Captcha For ASP.net MVC


Small tip with dynamic type view in ASP.MVC 2


ASP.NET MVC 2 allows you use dynamic type view by using System.Web.Mvc.ViewPage<dynamic> (or someone call this case is anonymous type view). When you use dynamic type view, the Model property is a dynamic-object which is resolved at the runtime.

But, you will meet something wrong when you use dynamic type view. Example, Display is a dynamic type view in my ASP.NET MVC 2 Web Application. And in my action, I do something like this

Code Snippet
  1. return View("Display",
  2.     new
  3.     {
  4.         Mode = "Error",
  5.         Message = "This Student ID has already registered by someone."
  6.     });

I return Display view and pass an anonymous object as a model to Display view. And this is my Display view

Code Snippet
  1. <div>
  2.     <%: Model.Mode %>
  3.     <%: Model.Message %>
  4. </div>

Everything looks good. But, when you run this application and navigate to Display view, you will get RuntimeBinderException exception with message {‘object’ does not contain definition for ‘Mode’}. And it’s more crazy when you switch to debug mode and watch Model property, there are existing Mode and Message properties! So, what’s wrong here?

The answer is Anonymous type is internal, it means its properties, methods can’t be access from outside its assembly. (You can read more explaination at http://www.heartysoft.com/post/2010/05/26/anonymous-types-c-sharp-4-dynamic.aspx)

The solution for my case is very simple like this

Code Snippet
  1. <div>
  2.     <%: Model.GetType().GetProperty("Mode").GetValue(Model, null) %>
  3.     <%: Model.GetType().GetProperty("Message").GetValue(Model, null)%>
  4. </div>

And everything working well.

Supporting multiple submit buttons on an ASP.NET MVC view


A while ago, I was asked for advice on how to support multiple submit buttons in an ASP.NET MVC application, preferably without using any JavaScript. The idea was that a form could contain more than one submit button issuing a form post to a different controller action.

The above situation can be solved in many ways, one a bit cleaner than the other. For example, one could post the form back to one action method and determine which method should be called from that action method. Good solution, however: not standardized within a project and just not that maintainable… A better solution in this case was to create an ActionNameSelectorAttribute.

Whenever you decorate an action method in a controller with the ActionNameSelectorAttribute (or a subclass), ASP.NET MVC will use this attribute to determine which action method to call. For example, one of the ASP.NET MVC ActionNameSelectorAttribute subclasses is the ActionNameAttribute. Guess what the action name for the following code snippet will be for ASP.NET MVC:

public class HomeController : Controller
{
    [ActionName("Index")]
    public ActionResult Abcdefghij()
    {
        return View();
    }
}

That’s correct: this action method will be called Index instead of Abcdefghij. What happens at runtime is that ASP.NET MVC checks the ActionNameAttribute and asks if it applies for a specific request. Now let’s see if we can use this behavior for our multiple submit button scenario.

The view

Since our view should not be aware of the server-side plumbing, we can simply create a view that looks like this.

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<MvcMultiButton.Models.Person>" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "//www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Create person</title>
    <script src="<%=Url.Content("~/Scripts/MicrosoftAjax.js")%>" type="text/javascript"></script>
    <script src="<%=Url.Content("~/Scripts/MicrosoftMvcAjax.js")%>" type="text/javascript"></script>
</head>
<body>

    <% Html.EnableClientValidation(); %>
    <% using (Html.BeginForm()) {%>

        <fieldset>
            <legend>Create person</legend>
            <p>
                <%= Html.LabelFor(model => model.Name) %>
                <%= Html.TextBoxFor(model => model.Name) %>
                <%= Html.ValidationMessageFor(model => model.Name) %>
            </p>
            <p>
                <%= Html.LabelFor(model => model.Email) %>
                <%= Html.TextBoxFor(model => model.Email) %>
                <%= Html.ValidationMessageFor(model => model.Email) %>
            </p>
            <p>
                <input type="submit" value="Cancel" name="action" />
                <input type="submit" value="Create" name="action" />
            </p>
        </fieldset>

    <% } %>

    <div>
        <%=Html.ActionLink("Back to List", "Index") %>
    </div>

</body>
</html>

Note the two submit buttons (namely “Cancel” and “Create”), both named “action” but with a different value attribute.

The controller

Our controller should also not contain too much logic for determining the correct action method to be called. Here’s what I propose:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new Person());
    }

    [HttpPost]
    [MultiButton(MatchFormKey="action", MatchFormValue="Cancel")]
    public ActionResult Cancel()
    {
        return Content("Cancel clicked");
    }

    [HttpPost]
    [MultiButton(MatchFormKey = "action", MatchFormValue = "Create")]
    public ActionResult Create(Person person)
    {
        return Content("Create clicked");
    }
}

Some things to note:

  • There’s the Index action method which just renders the view described previously.
  • There’s a Cancel action method which will trigger when clicking the Cancel button.
  • There’s a Create action method which will trigger when clicking the Create button.

Now how do these last two work… You may also have noticed the MultiButtonAttribute being applied. We’ll see the implementation in a minute. In short, this is a subclass for the ActionNameSelectorAttribute, triggering on the parameters MatchFormKey and MatchFormValues. Now let’s see how the MultiButtonAttribute class is built…

The MultiButtonAttribute class

Now do be surprised of the amount of code that is coming…

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultiButtonAttribute : ActionNameSelectorAttribute
{
    public string MatchFormKey { get; set; }
    public string MatchFormValue { get; set; }

    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
    {
        return controllerContext.HttpContext.Request[MatchFormKey] != null &&
            controllerContext.HttpContext.Request[MatchFormKey] == MatchFormValue;
    }
}

When applying the MultiButtonAttribute to an action method, ASP.NET MVC will come and call the IsValidName method. Next, we just check if the MatchFormKey value is one of the request keys, and the MatchFormValue matches the value in the request. Simple, straightforward and re-usable.

Password Recovery in an ASP.NET MVC Project


Password Recovery in an ASP.NET MVC Project

While rewriting my personal web site with ASP.NET I noticed that although support for the ASP.NET Membership Provider comes included out of the box in a ASP.NET MVC project not all the options are fully implemented to the same extend that they are in a brand new ASP.NET WebForms project. For example, the option to reset your own password if you forgot your old one is not available out of the box in an ASP.NET MVC project.

Out of the box the following options are fully implemented in a ASP.NET MVC project:

  • Login in
  • Login out
  • Change your password
  • Create new user

Adding support for Password Recovery to an ASP.NET MVC project turned out to be pretty easy as the core functionality already exists in the Membership Provider and it’s just a matter of calling it from your application.

The process that I implemented goes like this:

  • From the LogOn view users can go to the PasswordReset view
  • In the PasswordReset the user indicates his/her username and then they are sent to the QuestionAndAnswer view
  • In the QuestionAndAnswer view the user enters the answer to their own security question
  • Finally, the user is sent to the PasswordResetFinal view with a message indicating that their password has been reset and e-mailed to them.

New Views and Controllers

LogOn

The first thing that I did was update the LogOn view that comes with ASP.NET and added a link to start the Password Reset process. I wired this link to the a new method called PasswordReset in the AccountController.

password_logon

Password Reset

Secondly I created the HTTP-GET PasswordReset method in the AccountController and a very simple view (PasswordReset.aspx) to allow the user to enter his/her username so that we can reset their password. The PasswordReset.aspx view is extremely simple as it only has a textbox where the user enter their user name.

password_reset

   1: public ActionResult PasswordReset()
   2: {
   3:     if (!MembershipService.PasswordResetEnabled) throw new Exception("Password reset is not allowed");
   4:     return View();
   5: } 

I also implemented an HTTP-POST PasswordReset method in the AccountController to pick up the data and continue the process. This controller method decides whether the next step is to reset the password for this user or if we need to ask him/her a Password Recovery question before we reset their password. This step is required to honor the requiresQuestionAndAnswer configuration setting in the ASP.NET Membership Provider.

   1: [HttpPost]
   2: public ActionResult PasswordReset(string userName)
   3: {
   4:     if (!MembershipService.PasswordResetEnabled) throw new Exception("Password reset is not allowed");
   5:  
   6:     if (MembershipService.RequiresQuestionAndAnswer)
   7:     {
   8:         return RedirectToAction("QuestionAndAnswer", new { userName = userName } );
   9:     }
  10:     else
  11:     {
  12:         MembershipService.ResetPassword(userName, GetLoginUrl());
  13:         return RedirectToAction("PasswordResetFinal", new { userName = userName });
  14:     }
  15: } 
Password Question and Answer

If the Membership Provider is configured to require a question and answer before resetting a user’s password then we route users to the QuestionAndAnswer view. This view is also very simple as it merely has two labels (one with the username and another with password question for the user) and a textbox where the user will enter the answer to their password question.

Security Question View

To support this QuestionAndAnswerView I implemented an HTTP-GET controller method that fetches the security question for the username entered in the PasswordReset view.

public ActionResult QuestionAndAnswer(string userName)
{
    if (!MembershipService.PasswordResetEnabled) throw new Exception("Password reset is not allowed");
    ViewData["UserName"] = userName;
    ViewData["Question"] = MembershipService.GetUserQuestion(userName);
    return View();
}

Finally I added an HTTP-POST method to support the QuestionAndAnswer. By the time we get to this HTTP-POST method we have all the information that we need to reset a user’s password (namely the user name and the answer to the security question.) Hence this method is where the call to actually reset the user’s password actually happens.

[HttpPost]
public ActionResult QuestionAndAnswer(string userName, string answer)
{
    if (!MembershipService.PasswordResetEnabled) throw new Exception("Password reset is not allowed");
MembershipService.ResetPassword(userName, answer, GetLoginUrl());
    return RedirectToAction("PasswordResetFinal", new { userName = userName });
}

Password Reset Final

The last step in the process if a new view called PasswordResetFinal that just displays a message to the user telling him/her that a new password has been generated and e-mailed to them.

Password Reset Final View

Changes to the Model

Every ASP.NET MVC project comes with a default model called AccountMembershipService to support the Views and Controllers that handle membership information. This AccountMembershipService is not much more than a wrapper for the ASP.NET MembershipProvider. Adding functionality to this model to support the password reset operation was very simple as the MembershipProvider already provides the core functions. The MembershipService referenced in the controller actions in the code actually point to an instance of this AccountMembershipService.

For example, the QuestionAndAnswer view calls the following method to to retrieve the security question for a user. Notice how this method’s only job is to forward the calls to the Membership provider.

public string GetUserQuestion(string userName)
{
MembershipUser user = _provider.GetUser(userName, false);
    if (user == null)
    {
        throw new Exception("User name not found");
    }
    else
    {
    return user.PasswordQuestion;
    }
}

All in all I added two methods to the AccountMembershipService (one to retrieve a user’s security question and one to actually do the password reset) plus a few properties to expose a couple of features of the Membership provider (like the need for a security question) that were not exposed on the default implementation.

In Summary…

As I indicated at the beginning of this blog post, adding support for Password Recovery to an ASP.NET MVC project turned out to be pretty easy as the core of the functionality already exists in the Membership Provider.