Archive | .NET RSS for this section

Angular Auto Form Validation Summary

One of the more mundane things in Angular is creating validation messages. If you come from .Net you’ll probably be screaming as your views have always just pumped out the validation based on Data Annotations from your class models/view models.

Well we’re one in the same because I hate writing the same thing over and over again. This is a quick directive I built to iterate over a collection and build out those little guys for me. It has a the ability to show a summary or display errors below the associated control. There are a few other options that affect behavior as to when the messages trigger or show.

See the Github page for installation and use instructions.

angular-validation-summary

Advertisements

Convert Windows Line Endings to Unix

From time to time I create projects that need to be supported by both Windows and *NIX systems. Line endings are always a pain and although GIT has helped this a lot there are still issues at times that you must deal with manually.

Here is a quick way to output a project and convert line endings all in one. This is not difficult but probably won’t make much sense unless you’ve messed with .cmd or .bat files in the past.

First grab dos2unix here. You’ll need to select the correct binary for your platform either 32bit or 64bit. As of today 6.0.3 is the current version.

Save the below to “your_project_name.bat”


@ECHO off
setlocal EnableDelayedExpansion

SET source=D:\Path\To\Your\Source
SET dest=D:\Path\To\Your\Destination
SET d2upath=D:\dos2unix\Folder\Path
CALL publish.bat -r -s %source% -d %dest%
cd %dest%
ECHO Converting files to unix line endings…
SET exts=*.json, *.js
FOR %%f in (%exts%) DO (
CALL %d2upath%\dos2unix %%f
)
ECHO Finished publishing.

Save the following to “publish.bat”


@ECHO off
setlocal EnableDelayedExpansion

REM Set npm path, default build directory various variables.
SET execDir=%~dp0
SET sourceDir=%CD%
SET destDir=
SET lastArg=0
SET sourceIdx=0
SET destIdx=0
SET ctr=0
SET empty=n

REM No args passed use defaults goto dependency check.
IF [%~1]==[] GOTO :HELP
IF /i %~1==-h GOTO :HELP

REM Loop over args and assign them.
:SETARGS
IF [%~1]==[] GOTO :DIREXISTS

IF /i %~1==-s (
SET /a sourceIdx=%ctr%+1
)
IF /i %~1==-d (
SET /a destIdx=%ctr%+1
)

IF /i %~1==-r (
SET empty=y
)

IF %ctr%==%sourceIdx% (
IF %lastArg%==-s SET sourceDir=%~1
)

IF %ctr%==%destIdx% (
IF %lastArg%==-d SET destDir=%~1
)

SET lastArg=%~1
SET /a ctr+=1
SHIFT & GOTO :SETARGS

:DIREXISTS
IF EXIST %destDir% GOTO :PUBLISH
SET /p create=”Directory “%destDir%” does not exist, would you like to create it (y or n)?”
IF %create%==y (
ECHO Creating directory…
MKDIR %destDir%
GOTO :PUBLISH
)
ECHO ——————————————————-
ECHO Nothing to do, no output directory exiting…
GOTO :END

:PUBLISH
ECHO PUBLISHING: %sourceDir% to %destDir%
ECHO ——————————————————-
IF %empty%==y (
ECHO Removing files and subfolders.
RD %destDir% /s /q
ECHO:
)
MKDIR %destDir%
ECHO Publishing please wait…
ECHO:
IF EXIST pubignore.txt (
XCOPY /s /y /EXCLUDE:pubignore.txt %sourceDir%\* %destDir%
) ELSE (
XCOPY /s /y %sourceDir%\* %destDir%
)
GOTO :END

:HELP
ECHO:
ECHO HELP: Publish Script Help.
ECHO ——————————————————-
ECHO -s (specify source, default is current directory.)
ECHO -d (specify destination directory [required].)
ECHO -r (remove directory and re-create)
GOTO :END

:END
ECHO:
ECHO FINISHED

Create a text file with the name “pubignore.txt”

In this file you can put in files you want it to ignore when it copies the files from your source to your destination that you created in “your_project_name.bat”. This uses the standard xcopy EXCLUDE formatting.

NOTE: in the “your_project_name.bat” you can specify file extension types that you want dos2unix to convert also do this where it says “SET exts=*.json, *.js”

Created the files now what?

So once you’ve created all three files you have a couple options. You can put them all in the same folder as your project and then cd D:\Your\Project\Path. Where again “D” is the drive your project is in, then simply run “your_project_name.bat”.

The other option is to create a “Scripts” folder somewhere on your machine and then dump all but pubignore.txt (it needs to go in the project folder itself) in that folder. Lastly in your environment variables for your path include the path to the scripts folder. You can do this using the link I provided above or through the command line. Just Google it if you don’t quite get it. Once you do that you can just run “your_project_name.bat” from anywhere and it will do its job. This is in fact what I do and often call it from Visual Studio on build.

DOWNLOAD dos2unix

Xipframe MVC Framework

Want to check out a cool NodeJs based framework? Checkout Xipframe! Not only does it build out many of the mundane tasks of building a web application for you but it also can enable LESS and AngularJS helpers for you as well. Tired of managing CSS and JavaScript file references? Xipframe does that for you too.

Check it out here.

Download it via NPM here.

Kendo UI MVC Grid Extention

Kendo UI MVC Grid Extention

Didn’t like how the MVC Destroy() method worked so I wired up my own. This is a simple function to help you get the row’s data when you fire off the custom event.

Essential “e” is the event that is fired from the custom command event in your Kendo UI grid. It takes the target and turns it into a JQuery object. It grabs the uid of the row and then matches it with the uid in the data. Its very quick and works nicely. Sure there are other ways to skin this cat but this seemed to be very smooth. Seems to me there should be a JQuery data object for the row but all I saw was the uid hence this function below. Anyway thought I’d share.

   function getRowData(e) {
        var target, grid, uid, row, data;
        target = $(e.target);
        uid = target.parent().parent().data().uid;      
        grid = $('#Grid').data('kendoGrid');        
        row = grid._data;
        for (var i = 0; i < row.length; i++) {
            if (row[i].uid == uid) {
                data = row[i];
            }
        }
        return data;
    }

What you’ll get is your data for the row as an object so you can simple do:
// note below assumes you have a custom column command that on .click() is attached to the “deleteRow” function “e” is the event that the click event will pass in.

function deleteRow(e){
	var data = getRowData(e);
	var name = data.full_name // where full_name is a column in your grid. 
	alert('Are you sure you want to delete ' + name);
	// TODO: wire up your delete event passing in an id or something
	$.post('/your/path', {}, function (result) {
		if(result.deleted){
			// load your UI or whatever
		}
	});
} 

The getRowData function you can copy/paste but the above “deleteRow” is just an example you’ll need to modify to your needs.

MVC3 Shared Hosting on GoDaddy

As of this post GoDaddy Shared Hosting does not directly support MVC3. Have no fear though you just need to add reference to a handful of libraries to make it all work. Additionally you’ll need to set these libraries to copy to local. Simply click the following references listed and set them from false to true as the images below. Note the post Phil Haack has additional details however the list posted there is missing a couple references follow his post but add the additional libraries. Again this may change once GoDaddy updates their shared hosting environment, so this may not be needed by the time your read this. It its not working well you know what to do then…

  • Microsoft.Web.Infrastructure
  • System.Web.Helpers
  • System.Web.Mvc
  • System.Web.Razor
  • System.Web.WebPages
  • System.Web.WebPages.Deployment
  • System.Web.WebPages.Razor
  • system.Web.Abstractions
  • System.Web.Routing

MVC Menu Generation Class

Menus can be complex or rather simple. Often if it is just a simple site with maybe one level of sub navigation and a handful of links it might be easiest to just throw in some divs, uls or whatever you choose and build up the menu manually in a partial view. When you start to add more items or they change frequently this is probably not the best solution. When the menu gets larger or every bit of speed counts you may want to cache it as well. So here is a class I put together this afternoon. It uses four things. A class, an Mvc helper method, some Css and a tiny bit of JQuery.

Quick Note

There are several other solutions out there for menus in MVC. You should check them out. The reason I did this is that I wanted to leverage the good ol’ SiteMapCollection and SiteMap built into .Net. Additionally something I think many don’t realize is in your Web.SiteMap you can add additional attributes to each node. For example in this article an additional attribute is used called “image”. This is used to add the dropdown arrow you see in the example below. So you could even change the whole structure with the exception of a few required attributes to do whatever you need. So rather than create a custom XML file that takes in controller = “home”, action = “index” and so on I chose to use the SiteMap for this one. Essentially “/Home/Index” is essentially the same thing. Obvious there are exceptions but for the most part this works just fine. The SiteMapCollection is rather easy to work with as you’ll see in this example. However you should as I mentioned check out some other examples. In fact this method in this article I’ve obviously used but have used several others also. Some links to checkout:

Alternate Menus To Checkout

MVC SiteMap Provider – This works great but may be more than you need. Used it many times.

Telerik MVC Menu – Telerik is great if you’re already using their controls.

Getting Started

Download Source

First thing you probably want to do is take a look at the class. All it is doing is building up some strings. You could create tags and so on but this was quick and does the job just fine for mvcmenu_screenshota simple solution to build out some unordered lists. Typically what you would use with JQuery. I won’t go over the whole class. It is heavily commented so you should have no trouble modifying or doing what you want. The biggest key to this class is the small method called “Process Child”. this is because it does some recursion which makes building out the menu possible when you don’t know exactly how many levels of sub menus we’d have in our SiteMap.

After you make reference (add using to your helper extensions class) to the MVCMenu.cs (see source) in your Helper Extensions class wherever that may be, you’ll need the following Helper to make it all wire up. Note may of the options here could be set statically in the method so that for example you don’t have to pass all those options. Maybe just enable security trimming. Most of these you’ll set once and forget anyway. It does help in development to have them all exposed to you in your view. If you need help creating a Helper or don’t understand what it is, please stop and go here first then come back.

Adding Helper Method

/// <summary>
/// Creates a menu by dynamically creating a string to be returned as MvcHtml.
/// </summary>
/// <param name="helper">The Html Helper.</param>
/// <param name="homeController">Your Home controller name, probably "Home" w/o quotes.</param>
/// <param name="useIcons">Indicates whether icon images should be used on parent menus.</param>
/// <param name="iconPosition">For icons on left 0, for right enter 1.</param>
/// <param name="enableDesc">Enables/Disables tooltips on anchors inside each menu. Use false in most cases.</param>
/// <param name="enableSecurityTrimming">Provides security trimming when enabled.</param>
/// <param name="enableCache">Adds caching to the MVCMenu, Selected CSS state will not work when enabled.</param>
/// <returns></returns>
public static MvcHtmlString MVCMenu(this HtmlHelper helper, string homeController, 
                                    bool useIcons, int iconPosition, bool enableDesc, 
                                    bool enableSecurityTrimming, bool enableCache)
{
    MVCMenu mu = new MVCMenu();
    string menu = mu.GetMenu(homeController, useIcons, iconPosition, 
                  enableDesc, enableSecurityTrimming, enableCache);
    return MvcHtmlString.Create(menu);
}

Reference Helper in View

So we have our Helper method in place, have referenced/resolved (Namespace = Origin1Technologies.MVC.Helpers)  MVCMenu.cs, now we’re ready to actually call the helper on our view. See syntax below. You’ll get intellisense as you specify the options. It will be self explanatory.

@Html.MVCMenu("Home", true, 1, false, false, false)

CSS Classes Built Into MVCMenu

There are few classes built into the MVCMenu.cs class. They are set as constants in the class. Feel free to change those to names/conventions that make more sense to you if you like but this is the defautl:

    • “menu-root” – This is the root of our menu. Not to be confused with the root of your SiteMap which is almost never used.
    • “menu-parent” – This is a ul that is nested in menu that has sub items regardless of nexted level.
    • “menuitem” – This is an item in the menu. Essentially an li, from root to the last child.
    • “menuitem-selected” – When a menuitem matches the current route (basically just the controller name) it is set as selected. See comments for a few exceptions and a better understanding.
    • “level{0}” – Where {0} is the level at which the “menu-parent” is at. Level 0 is the root of the menu. The next 1 and so on. This can be helpful in css styling. Often with a good JQuery design this is not needed but it can be handy.

There are several examples of using ul’s as menus. You can even grab some existing css from a menu plugin (JQuery) and use it or use the menu plugin. The majority use the very structure the Helper builds out. Just modify the CSS class names in the MVCMenu.cs class or your stylesheet to match up with the above described class names and bingo you have a menu.

Wrap Up

Now you have some todos. The Helper will build out an unordered list for you with nested unordered lists. You’ll need to wire up some css. That is up to you. The design of a menu is pretty specific to a site so that’ll be up to you. You can use CSS pseudo classes for hovering but probably better to use JQuery for better control. One HINT is to make sure the submenus are position: absolute in most cases for desired look.

    NOTE: this hasn’t been fully debugged. As with many of the things I post here that quick things thrown together for a need and I share them out. Often by the time you read this article the code will have been modified several times. Often I update the download links to the current source but may not always do so. So don’t get frustrated if something breaks just take a moment, do some proper debugging and you’ll be good. You can also comment and if I catch it in time I’ll gladly reply.

MVC Data Annotations and Custom Attributes

So you’ve found out that MVC supports Editor Templates. About 10 min. later you think to yourself hey how can I leverage this to automatically set my textbox width or maxlength and so on. This is a simple example using just a couple classes.

Create Abstract Class Inherit Attribute:

public abstract class MetadataAttribute : Attribute
{
    public abstract void Process(ModelMetadata metaData);
}  

This simply creates a void to process any attributes we set to inherit our class MetadataAttribute. The next piece of code will make sense of this.

Custom Attribute Class:

public class OptionalAttributes : MetadataAttribute
{
    public string style { get; set; }   
    public override void Process(ModelMetadata metaData)
    {
       metaData.AdditionalValues.Add("style", this.style);
    }
}

All we are doing here is creating a public property to store our Attribute value. In this case a simple style (you would probably do something much more relevant to your project this is just for demonstration purposes) Attribute is created. We then must implement our Process as this is inheriting MetadataAttribute.

Adding Attribute to View Model

[OptionalAttributes(style = "width: 150px")]
public string Email { get; set; }

Here we reference our OptionalAttributes Class and expose our style, nothing exciting here.

Wiring it All Up:

public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(
        IEnumerable<Attribute> attributes,
        Type containerType,
        Func<object> modelAccessor,
        Type modelType,
        string propertyName)
    {
        var metaData = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
        attributes.OfType<MetadataAttribute>().ToList().ForEach(x => x.Process(metaData));
        return metaData;
    }
}

This is the magic that makes it all work. Here we create our Metadata Provider inheriting from DataAnnotationsModelMetadataProvider. So basically any Attribute that is of type MetadataAttribute will be processed hence our first abstract class we created.

Adding our Provider to Global.asax:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    ModelMetadataProviders.Current = new CustomModelMetadataProvider();
}

Pretty straight forward right. Since we inherit DataAnnotationsModelMetadataProvider we can simply set the ModelMetadataProviders.Current to our CustomModelMetadataProvider;

Making it Happen in Our View:

@model String

    @if (!ViewData.ModelMetadata.AdditionalValues.ContainsKey("style"))
    {         
        @Html.TextBoxFor(model => model)
    }
    else
    {
        var width = ViewData.ModelMetadata.AdditionalValues["style"];   
        @Html.TextBoxFor(model => model, new { style = width })
    }  

O.K so a couple things here. First off what you see is an EDITOR TEMPLATE partial view (you could use the above syntax in your view as well however your model syntax would be more like model => model.Email where Email is the name of your property in your model). Not the strongly typed view itself. Please note that. If you notice the model is of type String.  You can make templates for DateTime or whatever you like. In this case we are describing a simple string. So what is happening here. Well with a simple if else we check to see if the Metadata Key is null. If it has a value we get the value for that key in this case the key is called “style” and then apply it to the style Attribute for our textbox. Now any property in our view model that has a valid value for our Optional Attribute style will be used. No more styling adding css whatever to our view it will all automatically wire up. Can’t stress enough this is NOT a real world implementation but rather just giving you the idea. For instance you should incorporate checking for nulls and correct types but you already know that.

If you really want to be slick create an HtmlHelper to further extend this and do away with the if else statement and handle it all in your HtmlHelper.

No code source on this one. Straight forward just copy from the above, be sure to include your usings or imports of namespaces and you’ll be good.

MVC Paging and Sorting

If you’re new to MVC this is undoubtably something you’ll face at some point. You’ve already been searching Google and found a few examples, heard about MVCContribWebGrid and Telerik. All of which are great solutions. However you want the manual control, don’t want to use tables or you just like to stay up late and bang keys on the keyboard. If that is the case this should at the very least get you on the right track. As with anything I post feel free to critique. It can only help. There are always alternate ways to skin a cat. This is no exception. Be advised as often with code I post I’ve significantly scaled this down to make it easy to follow striping out the application specific stuff.

So let’s get to it…

The first thing you’ll need to do is create a class that will handle your paging and sorting. This was expanded on from Scott Gu’s PaginatedList.cs class which didn’t support sorting but we’ll get to that.

PaginatedList.cs  (abbreviated)

Declare a few properties:

public int PageIndex { get; private set; } // Index of your current page.
public string SortExpression { get; private set; } // The expression to sort on ex: "FullName DESC".
public string SortColumn { get; private set; } // The column you are sorting on ex: "FullName".
public string SortDirection { get; private set; } // The direction to sort ex: "DESC" or "ASC".
public int PageDisplayIndex { get; private set; } // This is the page number to display. Show 1-5 instead of 0-4.
public int PageSize { get; private set; } // The amount of records per page.
public int TotalCount { get; private set; } // The total count of records.
public int TotalPages { get; private set; } // The total number of pages. TotalCount / PageSize
public int PagerSize { get; private set; } // The size of the pager. How many page numbers to show.
public int PagerStart { get; private set; } // The starting page in the pager numbers.
public int PagerEnd { get; private set; } // The ending page of the pager numbers.
public List<int> Pages { get; private set; } // The list of pager page numbers to display.

Let’s create the paginated list by passing a few variables from our controller (we’ll get to that shortly) .

public PaginatedList(IQueryable<T> source, string sortExpression, int pageIndex, int pageSize, int pagerSize)
{
    //Populate variables.
    PageIndex = pageIndex;
    SortExpression = sortExpression;
    PageDisplayIndex = pageIndex;
    PageSize = pageSize;
    PagerSize = pagerSize; 

    //Store the current Sort Column and Direction.
    //There should always be a value but just in case. This is for providing the current sort expression in our Model
    if (sortExpression != null && !string.IsNullOrEmpty(sortExpression))
    {
        string[] curSort = sortExpression.Split(' '); // Split so we can get the col and direction sep.
        SortColumn = curSort[0];

        //This could be null so let's check first.
        if (curSort.Length > 1)
        {
            SortDirection = curSort[1];
        }

    }       

    //Get the record count and the total pages.
    TotalCount = source.Count();
    TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);

    //Build out the list of pages available.
    BuildPagerPages(); // See below for this method. Creates the page numbers for the pager.

    //Finally Sort the data and select the page size.
    this.AddRange(source.OrderBy(sortExpression)
        .Skip((PageIndex - 1) * PageSize)
        .Take(PageSize));
}

Now we need a simple method to build out a list of the page numbers to display. Down load source, there are a couple other properties like “HasNextPage” or “HasPreviousPage” that you’ll need to check in your view or Html Helper to verify the link should be displayed.

/// <summary>
/// Creates list of page numbers for pager.
/// </summary>
private void BuildPagerPages()
{
    PagerStart = Math.Max(1, (PageIndex) - PagerSize / 2); // Start page of pager.
    PagerEnd = Math.Min(TotalPages, PageIndex + (PagerSize / 2)); // End page of pager.

    //Iterate through the total pages to make page selectors.
    Pages = new List<int>();
    for (var i = PagerStart; i <= PagerEnd; i++)
    {
        Pages.Add(i); // Add page number to list.
    }
}

DynamicOrderBy.cs (abbreviated)

Grabbed this in part from StackOverflow and Adam Anderson’s blog. It’s a great piece of code. I won’t bore you It is included in the source below but checkout the links for further details. Essentially though this class allows us to do our Linq OrderBy with ease.

Let’s Wire Up Our Controller

Probably the easiest part of the whole process. All we are doing here is grabbing some values from our route grabbing our data from our repository or where ever and then create our new instance of our PaginatedList class.

public ActionResult Index(string sort, int? page )
{
    // Define the page and pager defaults.
    const int pageSize = 10;
    const int pagerSize = 10;

    // You could also set this as an optional value
    // and set the default that way too.
    if (sort == null || string.IsNullOrEmpty(sort))
    {
        sort = "FullName ASC";
    }

    var accounts = repository.GetList(); // This is simply getting your data. That’s up to you, in this case a repository.
    // Note the page number default there. It is 1 instead of 0. This gets changed in our PaginatedList
    // There are a couple ways you could do this but the point is to have page numbers 1-5 instead of 0-5.
    var paginatedAccounts = new PaginatedList<Account>(accounts, sort, page ?? 1, pageSize, pagerSize);          

    return View("Index", paginatedAccounts);
}

Setup Routing

The simplest way to do this is of course in your Global.asax. Or you may have some other custom mechanisim to handle your routes as I do. But for this will show you the Global.asax method for brevity. This is very straight forward. Simply create a new route like below. Essentially you’d have something like “Clients/{sort}/{page}”.

routes.MapRoute(
    "ClientList", // Route name
    "Clients/{sort}/{page}", // URL with parameters
    new { controller = "Account", action = "Index" } // Parameter defaults
);

Last Piece of Puzzle the View and Helper

Updating the view is very easy. The below is abbreviated showing only the Previous button but you could easily add Next, First and Last if you wanted. So there are a couple ways you could do this. You could use JQuery and build out the url or you can use a Helper. There may be reasons why you might use one over the other but I’m going to show the Helper method. Remember in our PaginatedList class we had some properties called SortColumn, SortExpression and SortDirection right? Well now we need them. You could just pass the SortExpression only if you wanted and then split it up in your class however I chose to expose the expression and also the col and exp separately also. So in our link we simply need to pass in the current or last sort details plus the newly selected details. This is simply for one reason and that is if the column name passed is the same as the previous we need simply to sort the opposite way. If the column is a different one or no column exists we just sort ascending. Simple right. May see complicated but isn’t just read the comments it all makes sense.

public static MvcHtmlString SortActionLink(this HtmlHelper helper, string text, string actionName,
    string controller, string curColumn, string curDirection, string tag, object routeValues, object htmlAttributes)
{
    string sortResult, sortCol, sortExpression, sortDir, tagType;
    tagType = "a";
    sortExpression = "{0} {1}"; //Just because I don't want to write + ' ' +...ohh wait I just did. LOL.
    RouteValueDictionary routeDict = new RouteValueDictionary(routeValues);

    //Check for null. If this is the first time the page is loaded
    // if so we don't need to update the sort values.
    if (routeDict["sort"] != null)
    {
        sortCol = routeDict["sort"].ToString();
        if (string.Equals(curColumn, sortCol, StringComparison.CurrentCultureIgnoreCase))
        {
            //We're sorting the same column
            //Check the direction and then supply the opposite.
            switch (curDirection)
            {
                case "ASC":
                    sortDir = "DESC";
                    break;
                case "DESC":
                    sortDir = "ASC";
                    break;
                default:
                    sortDir = "ASC";
                    break;
            }
            sortResult = string.Format(sortExpression, sortCol, sortDir);
        }
        else
        {
            //This is a new column pass the default.
            //No need to append Sort Direction.
            //If no sort direction is supplied ASC is assumed.
            sortResult = sortCol;
        }

        //Update the Route data.
        routeDict["sort"] = sortResult;
        //helper.ViewContext.RequestContext.RouteData.Values["sort"] = sortResult;
    }

    var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
    var url = urlHelper.Action(actionName, controller, routeDict);

    if (tag != null)
        tagType = tag;

    TagBuilder build = new TagBuilder(tagType);
    build.MergeAttribute("href", url);
    build.InnerHtml = text;
    build.MergeAttributes(new RouteValueDictionary(htmlAttributes));
    return MvcHtmlString.Create(build.ToString(TagRenderMode.Normal));
}

…and the razor markup to make it all work. You are passing in the name for the link, then the action, then the controller. Then we pass in our current values of sort column and direction, then our Route Values which we modified based on the current sort values and finally any html attributes if needed.

@Html.SortActionLink("Full Name", "Index", "Client", Model.SortColumn, Model.SortDirection, new { sort = "FullName", page = Model.PageDisplayIndex }, null)

Wrap Up

To wrap up in your source you will notice that the First, Next etc links can be shown or hidden by a simple if statement that checks the HasPreviousPage bool value that we exposed in our PaginatedList class. Again see source for how to do this, if you don’t already.

Hope this helps you out,

C

Download Source

mvc_paging_sorting.rar