Handle multiple files upload in MVC3

To handle multiple files upload, we can pass a IEnumerable<HttpPostedFileBase> to our controller action method.

        [HttpPost]
        public ActionResult Create(IEnumerable files)
        {
             foreach (var file in files)
            {
                if (file != null && file.ContentLength > 0)
                {
                    
                    var fileName = Path.GetFileName(file.FileName);
                }
            return View();
        }

In the view you will have multiple file upload inputs with the same name.

@using(Html.BeginForm("Action","Controller", new { id = Model}, FormMethod.Post,new { enctype = "multipart/form-data"})){
      <div class="editor-label">
          
      </div>
      <div class="editor-label">
          
      </div>
      <div class="editor-label">
          
      </div>
      <div class="editor-label">
          
      </div>
      
}

Mvc will handle the rest for you.

How to implement paging & sorting in MVC 3 , Part 3 – How to use it for scroll loading like twitter

I have implemented a paging & sorting functions myself in mvc 3. It is extremely easy to integrate and extend, plus is required minimum codes in View & Action method. How cool is that. Let me show you how to use it with a simple example. Example can be downloaded here.

1. paging & sorting with postbacks (non-ajax)

I create a domain object People for demo purpose, it has 3 properties.

public class People
{
public int Id { get; set; }
public string Username { get; set; }
public int Age { get; set; }
}

View: ( the model of the view returns a IPagedList<People>)

javascript function loadMore is posting back to server to load data for the next page.

and $(window).scroll() is registered to trigger the loadMore function when scroll to bottom ( here I set 150px before reach the bottom, I find it have better UI experience, you can always adjust this by yourself)

@model MvcPaging.IPagedList<PagingSorting.Web.Models.People>
@using MvcPaging

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<script type="text/javascript">
    $(document).ready(function () {
        $(window).scrollTop('0px'); 
        var page = 2;
        var lock = false;
        $(window).scroll(function () {
            if ($(window).scrollTop() + 150 >= ($(document).height() - ($(window).height()))) {
                if (!lock) {
                    lock = true;
                    $('#scroll-loadingcontainer').show();
                    loadMore();
                }
            }
        });

        function loadMore() {
            $.post('@Url.Content("~/Scroll/AjaxPeople")', { Page: page }, function (data) {
                lock = false;
                page++;
                $('#gridcontainer .grid tbody').append(data);
                $('#scroll-loadingcontainer').hide();
            });
        }
    });
</script>

<div id="gridcontainer">
    <table class="grid">
<thead>
    <tr>
        <th>@Html.ActionLink("Id", "Index", Model)</th>
        <th>@Html.ActionLink("Username", "Index", Model)</th>
        <th>@Html.ActionLink("Age", "Index", Model)</th>
    </tr>
</thead>
<tbody>
    @Html.Partial("PeopleGridPartial",Model)
</tbody>
</table>
</div>

<div id="scroll-loadingcontainer">
</div>

PeopleGridPartial.cshtml (Partial view for ajax postback)

@model MvcPaging.IPagedList<PagingSorting.Web.Models.People>
@foreach (var i in Model) { 
    <tr>
        <td>@i.Id</td>
        <td>@i.Username</td>
        <td>@i.Age</td>
    </tr>
}

Controller Action:

a few things need to note here,

a. parameters need to be included for the method, page, sortBy, sortDescending

b. the pageIndex start with 0, if you pass null to “page”, it needs to be initiated as 0 for “PagingOption”

c. PeopleList is a IQueryable<People>, for your project you will get the list of items from repository.

d. convert the IQueryable<People> list to IPagedList by passing in the PagingOption.

        public ActionResult Index(int? Page, string SortBy, bool? SortDescending)
        {
            int currentPageIndex = Page.HasValue ? Page.Value - 1 : 0;
            var option = new PagingOption { Page = currentPageIndex, PageSize = PageSize, SortBy = SortBy, SortDescending = SortDescending };

            var items = PeopleList.ToPagedList(option);
            return View(items);
        }

        public ActionResult AjaxPeople(int? Page, string SortBy, bool? SortDescending)
        {
            int currentPageIndex = Page.HasValue ? Page.Value - 1 : 0;
            var option = new PagingOption { Page = currentPageIndex, PageSize = PageSize, SortBy = SortBy, SortDescending = SortDescending };
            var items = PeopleList.ToPagedList(option);

            return PartialView("PeopleGridPartial", items);
        }


Styles
: (You can refer to the last post as well)

style your div.pager, you can create styles for

.pager a (all the links in div.pager)

.pager .disabled (to display disabled link, eg. Previous or Next)

.pager .current (to display current page link)

.sort-off (sorting style for column header)

.sort-desc (descending style for column header)

.sort-asc (ascending style for column header)

.pager
{
	margin:8px 3px;
	padding:3px;
}

.pager .disabled
{
	border:1px solid #ddd;
	color:#999;
	margin-top:4px;
	padding:3px;
	text-align:center;
}

.pager .current
{
	background-color:#06c;
	border:1px solid #009;
	color:#fff;
	font-weight:bold;
	margin-top:4px;
	padding:3px 5px;
	text-align:center;
}

.pager span, .pager a
{
	margin: 4px 3px;
}

.pager a
{
	border:1px solid #c0c0c0;
	padding:3px 5px;
	text-align:center;
	text-decoration:none;
}

#loadingcontainer
{
position:absolute;
left:0;
top:0;
width:100%;
height:100%;
background:#efefef url('./images/ajax-loader.gif') no-repeat center center;
z-index:100;
opacity:0.8;
filter:alpha(opacity=80);
display:none;
}

#scroll-loadingcontainer
{
width:100%;
height: 50px;
background:#efefef url('./images/ajax-loader.gif') no-repeat center center;
z-index:100;
opacity:0.8;
filter:alpha(opacity=80);
display:none;
}

.grid
{
width:100%;
}

.grid td
{
line-height:35px;
}

.sort-off
{
width:100%;
display:inline-block;
background:none;
}

.sort-desc
{
width:100%;
display:inline-block;
background:transparent url('./images/icon_down_sort_arrow.png') no-repeat right center;
}

.sort-asc
{
width:100%;
display:inline-block;
background:transparent url('./images/icon_up_sort_arrow.png') no-repeat right center;
}

Images: (loading & sorting images)

You can download the example here: https://itsharp-mvc-pagingsorting.googlecode.com/svn/trunk/

How to implement paging & sorting in MVC 3 , Part 2– How to use it for Paging, Sorting

I have implemented a paging & sorting functions myself in mvc 3. It is extremely easy to integrate and extend, plus is required minimum codes in View & Action method. How cool is that. Let me show you how to use it with a simple example. Example can be downloaded here.

1. paging & sorting with postbacks (non-ajax)

I create a domain object People for demo purpose, it has 3 properties.

public class People
{
public int Id { get; set; }
public string Username { get; set; }
public int Age { get; set; }
}

View

: ( the model of the view returns a IPagedList<People>)

@model MvcPaging.IPagedList<PagingSorting.Web.Models.People>
@using MvcPaging
<table class="grid">
<thead>
<tr>
<th>@Html.ActionLink("Id", "Index", Model)</th>
<th>@Html.ActionLink("Username", "Index", Model)</th>
<th>@Html.ActionLink("Age", "Index", Model)</th>
</tr>
</thead>
<tbody>
@foreach (var i in Model) {
<tr>
<td>@i.Id</td>
<td>@i.Username</td>
<td>@i.Age</td>
</tr>
}
</tbody>
</table>

<div class="pager">
	@Html.Pager(Model.PageSize, Model.PageNumber, Model.TotalItemCount, Model.SortBy, Model.SortDescending)
</div>

Controller Action:

a few things need to note here,

a. parameters need to be included for the method, page, sortBy, sortDescending

b. the pageIndex start with 0, if you pass null to “page”, it needs to be initiated as 0 for “PagingOption”

c. PeopleList is a IQueryable<People>, for your project you will get the list of items from repository.

d. convert the IQueryable<People> list to IPagedList by passing in the PagingOption.

[HttpGet]
public ActionResult Index(int? Page, string SortBy, bool? SortDescending)
{
int currentPageIndex = Page.HasValue ? Page.Value - 1 : 0;
var option = new PagingOption { Page = currentPageIndex, PageSize = PageSize, SortBy = SortBy, SortDescending = SortDescending };

var items = PeopleList.ToPagedList(option);
return View(items);
}

Styles:

style your div.pager, you can create styles for

.pager a (all the links in div.pager)

.pager .disabled (to display disabled link, eg. Previous or Next)

.pager .current (to display current page link)

.sort-off (sorting style for column header)

.sort-desc (descending style for column header)

.sort-asc (ascending style for column header)

.pager
{
	margin:8px 3px;
	padding:3px;
}

.pager .disabled
{
	border:1px solid #ddd;
	color:#999;
	margin-top:4px;
	padding:3px;
	text-align:center;
}

.pager .current
{
	background-color:#06c;
	border:1px solid #009;
	color:#fff;
	font-weight:bold;
	margin-top:4px;
	padding:3px 5px;
	text-align:center;
}

.pager span, .pager a
{
	margin: 4px 3px;
}

.pager a
{
	border:1px solid #c0c0c0;
	padding:3px 5px;
	text-align:center;
	text-decoration:none;
}

#loadingcontainer
{
position:absolute;
left:0;
top:0;
width:100%;
height:100%;
background:#efefef url('./images/ajax-loader.gif') no-repeat center center;
z-index:100;
opacity:0.8;
filter:alpha(opacity=80);
display:none;
}

.grid
{
width:100%;
}

.sort-off
{
width:100%;
display:inline-block;
background:none;
}

.sort-desc
{
width:100%;
display:inline-block;
background:transparent url('./images/icon_down_sort_arrow.png') no-repeat right center;
}

.sort-asc
{
width:100%;
display:inline-block;
background:transparent url('./images/icon_up_sort_arrow.png') no-repeat right center;
}

Images

: (loading & sorting images)

2. paging & sorting with ajax

It is slightly different from the way we implemented for postbacks.

View:

Index.cshtml, it renders the partial view which is the grid itself

a. specify the target div with id “gridcontainer” and loading div id “loadingcontainer”

b. render the partial view and pass model to it

@model MvcPaging.IPagedList<PagingSorting.Web.Models.People>
<div id="gridcontainer">
@Html.Partial("PeopleGridPartial",Model)
</div>

<div id="loadingcontainer">
</div>

PeopleGridPartial.cshtml

a. using Ajax.ActionLink override method to create clickable column heading

b. UpdateTargetId & LoadingElementId must be same as the div IDs you defined in above view.

c. using Ajax.Pager override method to create the ajax paging links

@model MvcPaging.IPagedList<PagingSorting.Web.Models.People>
@using MvcPaging

<table class="grid">
<thead>
<tr>
<th>@Ajax.ActionLink("Id", "AjaxPeople", Model, new AjaxOptions { UpdateTargetId = "gridcontainer", LoadingElementId = "loadingcontainer" })</th>
<th>@Ajax.ActionLink("Username", "AjaxPeople", Model, new AjaxOptions { UpdateTargetId = "gridcontainer", LoadingElementId = "loadingcontainer" })</th>
<th>@Ajax.ActionLink("Age", "AjaxPeople", Model, new AjaxOptions { UpdateTargetId = "gridcontainer", LoadingElementId = "loadingcontainer" })</th>
</tr>
</thead>
<tbody>
@foreach (var i in Model) {
<tr>
<td>@i.Id</td>
<td>@i.Username</td>
<td>@i.Age</td>
</tr>
}
</tbody>
</table>

<div class="pager">
 @Ajax.Pager(Model.PageSize, Model.PageNumber, Model.TotalItemCount, Model.SortBy, Model.SortDescending, "AjaxPeople", new AjaxOptions { UpdateTargetId = "gridcontainer", LoadingElementId = "loadingcontainer" })
</div>

Controller Action:

a. AjaxPeople is taken care of by unobtrusive-ajax, to refresh the UpdateTargetId div

b. I make the thread sleep for 500ms, so you can see the loading screen. (remove it in production)

[HttpGet]
public ActionResult Index()
{
var option = new PagingOption { Page = 0, PageSize = PageSize };
var items = PeopleList.ToPagedList(option);
return View(items);
}

[HttpGet]
public ActionResult AjaxPeople(int? Page, string SortBy, bool? SortDescending)
{
int currentPageIndex = Page.HasValue ? Page.Value - 1 : 0;
var option = new PagingOption { Page = currentPageIndex, PageSize = PageSize, SortBy = SortBy, SortDescending = SortDescending };
var items = PeopleList.ToPagedList(option);

System.Threading.Thread.Sleep(500);

return PartialView("PeopleGridPartial", items);
}

_Layout.cshtml

You need to reference the unobtrusive-ajax.min.js

<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>

—————————————–

You can download the example here: https://itsharp-mvc-pagingsorting.googlecode.com/svn/trunk/

How to implement paging & sorting in MVC 3 , Part 1

I have implemented a paging & sorting functions myself in mvc 3. It is extremely easy to integrate and extend, plus is required minimum codes in View & Action method. How cool is that. Let me show you how to use it with a simple example. Example can be downloaded here.

Here we discuss how I implement the PagedList, PagingOption
and Pager and a few extension methods.

PagingOption.cs (using it to pass around the paging & sorting properties)

public class PagingOption : IPagingOption
{
public int Page { get; set; }
public int PageSize { get; set; }
public int? TotalCount { get; set; }
public string SortBy { get; set; }
public bool? SortDescending { get; set; }

public string OrderByExpression
{
get{
return this.SortDescending.HasValue && this.SortDescending.Value ? this.SortBy + " desc" : this.SortBy + " asc";
}
}
}

Pager.cs (it creates the pager html)

	public class Pager
	{
		private ViewContext viewContext;
		private readonly int pageSize;
		private readonly int currentPage;
		private readonly int totalItemCount;
		private readonly RouteValueDictionary linkWithoutPageValuesDictionary;
		private readonly AjaxOptions ajaxOptions;
private readonly string sortBy;
private readonly bool? sortDescending;

		public Pager(ViewContext viewContext, int pageSize, int currentPage, int totalItemCount, RouteValueDictionary valuesDictionary, AjaxOptions ajaxOptions)
		{
			this.viewContext = viewContext;
			this.pageSize = pageSize;
			this.currentPage = currentPage;
			this.totalItemCount = totalItemCount;
			this.linkWithoutPageValuesDictionary = valuesDictionary;
			this.ajaxOptions = ajaxOptions;
		}

public Pager(ViewContext viewContext, int pageSize, int currentPage, int totalItemCount, string sortBy, bool? sortDescending, RouteValueDictionary valuesDictionary, AjaxOptions ajaxOptions)
{
this.viewContext = viewContext;
this.pageSize = pageSize;
this.currentPage = currentPage;
this.totalItemCount = totalItemCount;
this.sortBy = sortBy;
this.sortDescending = sortDescending;
this.linkWithoutPageValuesDictionary = valuesDictionary;
this.ajaxOptions = ajaxOptions;
}

		public HtmlString RenderHtml()
		{
			var pageCount = (int)Math.Ceiling(totalItemCount / (double)pageSize);
			const int nrOfPagesToDisplay = 10;

			var sb = new StringBuilder();

			// Previous
			sb.Append(currentPage > 1 ? GeneratePageLink("&lt;", currentPage - 1) : "<span class=\"disabled\">&lt;</span>");

			var start = 1;
			var end = pageCount;

			if (pageCount > nrOfPagesToDisplay)
			{
				var middle = (int)Math.Ceiling(nrOfPagesToDisplay / 2d) - 1;
				var below = (currentPage - middle);
				var above = (currentPage + middle);

				if (below < 4)
				{
					above = nrOfPagesToDisplay;
					below = 1;
				}
				else if (above > (pageCount - 4))
				{
					above = pageCount;
					below = (pageCount - nrOfPagesToDisplay);
				}

				start = below;
				end = above;
			}

			if (start > 3)
			{
				sb.Append(GeneratePageLink("1", 1));
				sb.Append(GeneratePageLink("2", 2));
				sb.Append("...");
			}

			for (var i = start; i <= end; i++)
			{
				if (i == currentPage || (currentPage <= 0 && i == 0))
				{
					sb.AppendFormat("<span class=\"current\">{0}</span>", i);
				}
				else
				{
					sb.Append(GeneratePageLink(i.ToString(), i));
				}
			}
			if (end < (pageCount - 3))
			{
				sb.Append("...");
				sb.Append(GeneratePageLink((pageCount - 1).ToString(), pageCount - 1));
				sb.Append(GeneratePageLink(pageCount.ToString(), pageCount));
			}

			// Next
			sb.Append(currentPage < pageCount ? GeneratePageLink("&gt;", (currentPage + 1)) : "<span class=\"disabled\">&gt;</span>");

			return new HtmlString(sb.ToString());
		}

		private string GeneratePageLink(string linkText, int pageNumber)
		{
var pageLinkValueDictionary = new RouteValueDictionary(linkWithoutPageValuesDictionary) { {"Page",pageNumber},{"SortBy", sortBy},{"SortDescending",sortDescending} };

			// To be sure we get the right route, ensure the controller and action are specified.
			var routeDataValues = viewContext.RequestContext.RouteData.Values;
			if (!pageLinkValueDictionary.ContainsKey("controller") && routeDataValues.ContainsKey("controller"))
			{
				pageLinkValueDictionary.Add("controller", routeDataValues["controller"]);
			}
			if (!pageLinkValueDictionary.ContainsKey("action") && routeDataValues.ContainsKey("action"))
			{
				pageLinkValueDictionary.Add("action", routeDataValues["action"]);
			}

			// 'Render' virtual path.
			var virtualPathForArea = RouteTable.Routes.GetVirtualPathForArea(viewContext.RequestContext, pageLinkValueDictionary);

			if (virtualPathForArea == null)
				return null;

			var stringBuilder = new StringBuilder("<a");

			if (ajaxOptions != null)
				foreach (var ajaxOption in ajaxOptions.ToUnobtrusiveHtmlAttributes())
					stringBuilder.AppendFormat(" {0}=\"{1}\"", ajaxOption.Key, ajaxOption.Value);

			stringBuilder.AppendFormat(" href=\"{0}\">{1}</a>", virtualPathForArea.VirtualPath, linkText);

			return stringBuilder.ToString();
		}
	}

PagedList.cs (when you retrieve the IQueryable<T> list from repository, and convert it to IPagedList<T>, and use this for the view’s model, so the model will contains all the paging & sorting information)

	public class PagedList<T> : List<T>, IPagedList<T>
	{
		public PagedList(IEnumerable<T> source, IPagingOption option)
: this(source.AsQueryable(), option)
		{
		}

public PagedList(IQueryable<T> source, IPagingOption option)
		{
if(option==null)
throw new ArgumentOutOfRangeException("PagingOption", "Value can not be null.");
			if (option.Page < 0)
				throw new ArgumentOutOfRangeException("index", "Value must be greater than 0.");
			if (option.PageSize < 1)
				throw new ArgumentOutOfRangeException("pageSize", "Value must be greater than 1.");

			if (source == null)
				source = new List<T>().AsQueryable();

			var realTotalCount = source.Count();

			PageSize = option.PageSize;
			PageIndex = option.Page;
			TotalItemCount = option.TotalCount.HasValue ? option.TotalCount.Value : realTotalCount;
			PageCount = TotalItemCount > 0 ? (int)Math.Ceiling(TotalItemCount / (double)PageSize) : 0;

SortBy = option.SortBy;
SortDescending = option.SortDescending;
if(!string.IsNullOrEmpty(option.SortBy))
source = source.OrderBy(option.OrderByExpression);

			HasPreviousPage = (PageIndex > 0);
			HasNextPage = (PageIndex < (PageCount - 1));
			IsFirstPage = (PageIndex <= 0);
			IsLastPage = (PageIndex >= (PageCount - 1));

			if (TotalItemCount <= 0)
				return;

			var realTotalPages = (int)Math.Ceiling(realTotalCount / (double)PageSize);

			if (realTotalCount < TotalItemCount && realTotalPages <= PageIndex)
				AddRange(source.Skip((realTotalPages - 1) * PageSize).Take(PageSize));
			else
				AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
		}

		#region IPagedList Members

		public int PageCount { get; private set; }
		public int TotalItemCount { get; private set; }
		public int PageIndex { get; private set; }
		public int PageNumber { get { return PageIndex + 1; } }
		public int PageSize { get; private set; }
		public bool HasPreviousPage { get; private set; }
		public bool HasNextPage { get; private set; }
		public bool IsFirstPage { get; private set; }
		public bool IsLastPage { get; private set; }
public string SortBy{ get; private set; }
public bool? SortDescending { get; private set; }
		#endregion
}

PagingExtensions.cs (here are the extension methods to help you generate the html for the view)

public static class PagingExtensions
{
#region AjaxHelper extensions

public static HtmlString Pager(this AjaxHelper ajaxHelper, int pageSize, int currentPage, int totalItemCount, AjaxOptions ajaxOptions)
{
return Pager(ajaxHelper, pageSize, currentPage, totalItemCount, null, null, ajaxOptions);
}

public static HtmlString Pager(this AjaxHelper ajaxHelper, int pageSize, int currentPage, int totalItemCount,string sortBy, bool? sortDescending,string actionName, AjaxOptions ajaxOptions)
{
return Pager(ajaxHelper, pageSize, currentPage, totalItemCount, sortBy, sortDescending,actionName, null, ajaxOptions);
}

public static HtmlString Pager(this AjaxHelper ajaxHelper, int pageSize, int currentPage, int totalItemCount, string actionName, AjaxOptions ajaxOptions)
{
return Pager(ajaxHelper, pageSize, currentPage, totalItemCount, actionName, null, ajaxOptions);
}

public static HtmlString Pager(this AjaxHelper ajaxHelper, int pageSize, int currentPage, int totalItemCount, object values, AjaxOptions ajaxOptions)
{
return Pager(ajaxHelper, pageSize, currentPage, totalItemCount, null, new RouteValueDictionary(values), ajaxOptions);
}

public static HtmlString Pager(this AjaxHelper ajaxHelper, int pageSize, int currentPage, int totalItemCount, string actionName, object values, AjaxOptions ajaxOptions)
{
return Pager(ajaxHelper, pageSize, currentPage, totalItemCount,null,null, actionName, new RouteValueDictionary(values), ajaxOptions);
}

public static HtmlString Pager(this AjaxHelper ajaxHelper, int pageSize, int currentPage, int totalItemCount, RouteValueDictionary valuesDictionary, AjaxOptions ajaxOptions)
{
return Pager(ajaxHelper, pageSize, currentPage, totalItemCount, null, valuesDictionary, ajaxOptions);
}

public static HtmlString Pager(this AjaxHelper ajaxHelper, int pageSize, int currentPage, int totalItemCount, string sortBy, bool? sortDescending, string actionName, RouteValueDictionary valuesDictionary, AjaxOptions ajaxOptions)
{
if (valuesDictionary == null)
{
valuesDictionary = new RouteValueDictionary();
}
if (actionName != null)
{
if (valuesDictionary.ContainsKey("action"))
{
throw new ArgumentException("The valuesDictionary already contains an action.", "actionName");
}
valuesDictionary.Add("action", actionName);
}
var pager = new Pager(ajaxHelper.ViewContext, pageSize, currentPage, totalItemCount,sortBy, sortDescending, valuesDictionary, ajaxOptions);
return pager.RenderHtml();
}

#endregion

#region HtmlHelper extensions

public static HtmlString Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount)
{
return Pager(htmlHelper, pageSize, currentPage, totalItemCount, null, null);
}

public static HtmlString Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount, string sortBy, bool? sortDescending)
{
return Pager(htmlHelper, pageSize, currentPage, totalItemCount, sortBy, sortDescending, null, null);
}

public static HtmlString Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount, string actionName)
{
return Pager(htmlHelper, pageSize, currentPage, totalItemCount, actionName, null);
}

public static HtmlString Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount, object values)
{
return Pager(htmlHelper, pageSize, currentPage, totalItemCount, null, new RouteValueDictionary(values));
}

public static HtmlString Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount, string actionName, object values)
{
return Pager(htmlHelper, pageSize, currentPage, totalItemCount, null,null,actionName, new RouteValueDictionary(values));
}

public static HtmlString Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount, RouteValueDictionary valuesDictionary)
{
return Pager(htmlHelper, pageSize, currentPage, totalItemCount, null, valuesDictionary);
}

public static HtmlString Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount, string sortBy, bool? sortDescending, string actionName)
{
return Pager(htmlHelper, pageSize, currentPage, totalItemCount, sortBy, sortDescending, actionName, null);
}

public static HtmlString Pager(this HtmlHelper htmlHelper, int pageSize, int currentPage, int totalItemCount, string sortBy, bool? sortDescending, string actionName, RouteValueDictionary valuesDictionary)
{
if (valuesDictionary == null)
{
valuesDictionary = new RouteValueDictionary();
}
if (actionName != null)
{
if (valuesDictionary.ContainsKey("action"))
{
throw new ArgumentException("The valuesDictionary already contains an action.", "actionName");
}
valuesDictionary.Add("action", actionName);
}
var pager = new Pager(htmlHelper.ViewContext, pageSize, currentPage, totalItemCount,sortBy, sortDescending, valuesDictionary, null);
return pager.RenderHtml();
}

#endregion

#region ActionLink extensions

public static HtmlString ActionLink<T>(this AjaxHelper ajaxHelper, string name, string actionName, IPagedList<T> model, AjaxOptions ajaxOptions)
{
var sortDescending = model.SortBy != null && model.SortBy.Equals(name) && model.SortDescending.HasValue && model.SortDescending.Value ? false : true;
var routeValues = new { SortBy = name, SortDescending = sortDescending };

var css = "";
if (!string.IsNullOrEmpty(model.SortBy) && model.SortBy.Equals(name))
{
if (model.SortDescending.HasValue && model.SortDescending.Value)
css = "sort-desc";
else
css = "sort-asc";
}
else
css = "sort-off";
var htmlAttributes = new { @class = css };
return ajaxHelper.ActionLink(name, actionName, routeValues, ajaxOptions, htmlAttributes);
}

public static HtmlString ActionLink<T>(this HtmlHelper htmlHelper, string name, string actionName, IPagedList<T> model)
{
var sortDescending = model.SortBy != null && model.SortBy.Equals(name) && model.SortDescending.HasValue && model.SortDescending.Value ? false : true;
var routeValues = new { SortBy = name, SortDescending = sortDescending };

var css = "";
if (!string.IsNullOrEmpty(model.SortBy) && model.SortBy.Equals(name))
{
if (model.SortDescending.HasValue && model.SortDescending.Value)
css = "sort-desc";
else
css = "sort-asc";
}
else
css = "sort-off";
var htmlAttributes = new { @class = css };
return htmlHelper.ActionLink(name, actionName, routeValues,htmlAttributes);
}

#endregion

#region IQueryable<T> extensions

public static IPagedList<T> ToPagedList<T>(this IQueryable<T> source, IPagingOption option)
{
return new PagedList<T>(source, option);
}

#endregion

#region IEnumerable<T> extensions

public static IPagedList<T> ToPagedList<T>(this IEnumerable<T> source, IPagingOption option)
{
return new PagedList<T>(source, option);
}

#endregion
}

You will need to download the System.Linq.Dynamic from Microsoft site and reference the namespace to your project. Downloaded from: http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx For info on using Dynamic LINQ, see http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

You can download the example here: https://itsharp-mvc-pagingsorting.googlecode.com/svn/trunk/