C# – Convert HTML to PDF using Pechkin (WkHtmlToPdf)

HTMLtoPDFThere are a lot of C# libraries that can be used to create PDF files but I needed one that was able to convert an HTML file (with CSS and images) into a PDF document. Searching for a solution I found good reviews for a free library called Pechkin so I decided to give it a try.

While using it I stumbled upon some little problems but in the end I was satisfied with the result hence this post where I will detail my experience with this library.

Pechkin is a .NET Wrapper for another library called WkHtmlToPdf that uses the WebKit engine to convert HTML pages to PDF… and this is a pretty good reason to give it a try. If not yet convinced, below is a description of WebKit taken from Wikipedia:

WebKit is a layout engine software component designed to allow web browsers to render web pages. It powers Google’s Chrome web browser versions up to 27, and Apple’s Safari web browser applications. As of November 2012 it has the most market share of any layout engine at over 40% of the browser market share — ahead of both the Trident engine used by Internet Explorer, and the Gecko engine used by Firefox.

It is also used as the basis for the experimental browser included with the Amazon Kindle e-book reader, as well as the default browser in the Apple iOS, Android, BlackBerry 10, and Tizen mobile operating systems.

For testing the library I created a simple web application that reads the HTML content at a given URL and generates a PDF file. You can download it from Codeplex: https://pechkinwebtest.codeplex.com/.Test web application After trying with different URLs I got:

Problems and Solutions:

  1. Gif images are not supported – I couldn’t find any solution for this one.
  2. With the original Pechkin library the DLLs used for rendering the PDF remain hanging in memory:
    • If used inside a web application, the libraries that generate the PDF remain loaded in the memory indefinitely after making a new deploy. For example, if you run the site using the Visual Studio built-in web server and then you generate a PDF file the libraries will get loaded into memory. If, after that, you rebuild and run the site using the same built-in server you will get some errors like the one below.

      Could not copy “C:\WebSite\libgcc_s_dw2-1.dll” to “bin\libgcc_s_dw2-1.dll”. Exceeded retry count of 10.

      So I had to stop and restart the the server. You’ll get the same behavior when deploying to IIS. More details about this issue here: https://github.com/gmanny/Pechkin/issues/12

    • Solution: Someone made a branch of the original project and solved this issue, so make sure to use this project https://github.com/tuespetre/Pechkin and not the original one that is here: https://github.com/gmanny/Pechkin
  3. Images under HTTPS are not rendered inside the PDF. Solution: Download and install OpenSSL on the server (More details here: http://www.openssl.org/related/binaries.html)

How to use it:

  1. Download and compile this branch of the Pechkin project: https://github.com/tuespetre/Pechkin (This will solve the DLL hanging problem :)). Or, if you’re lazy, you can download here the needed DLLs. Buid the updated pechkin library
  2. Inside your web application solution add a reference to: Common.Logging.dll and Pechkin.dll.
    Add references
  3. Add to the root of your solution the following DLLs and set from the properties window: Copy to output directory » Copy always.Copy always
  4. Now you can start generating PDF files from HTML strings. Below is a very simple example that does this:
    //Transform the HTML into PDF
    var pechkin = Factory.Create(new GlobalConfig());
    var pdf = pechkin.Convert(new ObjectConfig()
    						.SetLoadImages(true).SetZoomFactor(1.5)
    						.SetPrintBackground(true)
    						.SetScreenMediaType(true)
    						.SetCreateExternalLinks(true), html);
    
    //Return the PDF file
    Response.Clear();
    
    Response.ClearContent();
    Response.ClearHeaders();
    
    Response.ContentType = "application/pdf";
    Response.AddHeader("Content-Disposition", string.Format("attachment;filename=test.pdf; size={0}", pdf.Length));
    Response.BinaryWrite(pdf);
    
    Response.Flush();
    Response.End();
    
  5. After publishing it to IIS, select the application pool for your web site, then right-click and select Advanced Settings... Here make sure to enable 32-bit applications otherwise you will get this error:

    Could not load file or assembly ‘Pechkin’ or one of its dependencies. An attempt was made to load a program with an incorrect format.

    Application pool settings

The source code of the test application, together with the Pechkin DLLs and the OpenSSL installer (Win32OpenSSL_Light-1_0_1e.exe) can be downloaded from Codeplex.

Advertisements

Manipulating excel files in C#

C# Excel

Thanks to stackoverflow I found out that Microsoft.Office.Interop.Excel is not supported in server-side code such as Microsoft Active Server Pages (ASP), ASP.NET, DCOM, or a Windows NT service.

As an alternative, you can use a free SDK from Microsoft – Open XML SDK 2.5 for Office. It provides strongly-typed classes that can be used to manipulate documents that adhere to the Office Open XML File Formats Specification.

How to use it:

  1. Download and install the SDK.
  2. In Solution Explorer, right-click on References » Add Reference. On the .NET tab select WindowsBase and DocumentFormat.OpenXml.
    • If DocumentFormat.OpenXml is not available in the list, copy the dll file from GAC.
    • For .NET framework version 4.0 it is located under the folder %windir%\Microsoft.NET\assembly\GAC_MSIL\DocumentFormat.OpenXml
    • Copy DocumentFormat.OpenXml.dll into your solution and add a reference to it.
  3. As an example I have created a simple method in C#, that you can use inside an asp.net web site to create an excel file in memory and then download it. The excel file created with this sample method contains one column with a header row and a content row. The header has a dark blue background and the text is white and appears in bold.
    Excel Example
public void DownloadExcelFile()
{
	var memoryStream = new MemoryStream();

	//Create a spreadsheet document in memory (by default the type is xlsx)
	using (var spreadsheetDocument = SpreadsheetDocument.Create(memoryStream, SpreadsheetDocumentType.Workbook))
	{
		//Add a WorkbookPart to the document. (The workbook element is the top level element. It contains elements and attributes that encompass the data content of the workbook.)
		var workbookpart = spreadsheetDocument.AddWorkbookPart();
		workbookpart.Workbook = new Workbook();
		workbookpart.Workbook.AppendChild(new FileVersion {ApplicationName = "Microsoft Office Excel"});

		//Add a WorksheetPart to the WorkbookPart.
		var worksheetPart = workbookpart.AddNewPart<WorksheetPart>();               
		worksheetPart.Worksheet = new Worksheet();

		//Set a custom width for the first column
		var columns = new Columns();
		columns.AppendChild(new Column { Min = 1, Max = 1, CustomWidth = true, Width = 15 });
		worksheetPart.Worksheet.AppendChild(columns);

		//Important! We must associate the sheet data after the columns otherwise the file will fail to open.
		var sheetData = new SheetData();
		worksheetPart.Worksheet.AppendChild(sheetData);

		//Add styles to format the header cells
		var workbookStylesPart = workbookpart.AddNewPart<WorkbookStylesPart>();
		workbookStylesPart.Stylesheet = new Stylesheet(
			new Fonts(
				new Font(), // Index 0 - default font
				new Font(   // Index 1 - The bold white font
					new Bold(),
					new Color {Rgb = new HexBinaryValue {Value = "FFFFFF"}}
					)
				),
			new Fills(
				new Fill(new PatternFill {PatternType = PatternValues.None}),                                                                            // Index 0 - default fill (should be always none)
				new Fill(new PatternFill { PatternType = PatternValues.Gray125 }),                                                                       // Index 1 - it's required and should always be gray 125
				new Fill(new PatternFill(new ForegroundColor { Rgb = new HexBinaryValue { Value = "000080" } }) { PatternType = PatternValues.Solid })   // Index 2 - dark blue fill
				),
			new Borders(
				new Border( // Index 0 - The default border
					new LeftBorder(),
					new RightBorder(),
					new TopBorder(),
					new BottomBorder(),
					new DiagonalBorder())
				),
			new CellFormats(
				new CellFormat {FontId = 0, FillId = 0, BorderId = 0}, // Index 0 - default cell style
				new CellFormat {FontId = 1, FillId = 2, BorderId = 0, ApplyFont = true, ApplyFill = true, } // Index 1 - Blue background cell with white bold text
				)
			);               

		//Add Sheets to the Workbook. (Sheets: represents the collection of worksheets in the workbook. The sheets are the central structure within a workbook, and contain the text, numbers, dates, formulas, and other elements of a workbook.)
		var sheets = spreadsheetDocument.WorkbookPart.Workbook.AppendChild(new Sheets());

		//Append a new worksheet and associate it with the workbook.
		sheets.AppendChild(new Sheet
			{
				Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart),
				SheetId = 1,
				Name = "Sheet 1"
			});

		//Populate the sheet data
		var headerRow = sheetData.AppendChild(new Row());
		headerRow.AppendChild(new Cell
			{
				CellValue = new CellValue("Header text"),
				DataType = CellValues.String,
				StyleIndex = 1
			});

		var contentRow = sheetData.AppendChild(new Row());
		contentRow.AppendChild(new Cell
		{
			CellValue = new CellValue("Value"),
			DataType = CellValues.String
		});                               

		workbookpart.Workbook.Save();
	}
	
	Response.Clear();
	Response.CacheControl = "Private";
	Response.Cache.SetExpires(DateTime.Now.AddSeconds(30));
	Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
	Response.AddHeader("Content-Type", "application/octet-stream");
	Response.AppendHeader("content-disposition", string.Format("attachment; filename=\"test.xlsx\"; size={0}", memoryStream.Length));
	Response.Flush();            
	Response.BinaryWrite(memoryStream.ToArray());

	Response.Flush();
	Response.End(); 
}

MVC User Controls

Contents:

  1. Introduction
  2. Partial views
  3. Custom HtmlHelper methods
  4. Templated helpers
  5. Summary

1. Introduction Go top

The concept of user controls as we were used in the Web Forms projects doesn’t exist in MVC but we have three simmilar alternatives:

  1. Partial views
  2. HtmlHelper extentions
  3. Templated helpers

2. Partial views Go top

Partial views are similar in concept to normal views with the main difference that they can be embedded inside views. As an example, we will create a page that, for the currently logged user, displays the billing and the shipping address allowing the user to edit them.

Final result

We will continue the solution build in the previous posts, that you can download from Codeplex. Or, if you prefer, you can download directly the source code for the current post using this link.

First we will create a new table to store the addresses and we will modify the users table to have two references to the Addresses table, one for the billing and one for the shipping address.

Create a new table for storing addresses

Below is the SQL script for updating the DB:

CREATE TABLE [dbo].[Addresses](
	[AddressId] [int] IDENTITY(1,1) NOT NULL,
	[Street] [varchar](100) NOT NULL,
	[ZipCode] [varchar](50) NULL,
	[City] [varchar](100) NOT NULL,
	[Country] [varchar](100) NOT NULL,
 CONSTRAINT [PK_Addresses] PRIMARY KEY CLUSTERED
(
	[AddressId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
ALTER TABLE dbo.Addresses SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
ALTER TABLE dbo.Users ADD
	BillingAddressId int NULL,
	ShippingAddressId int NULL
GO
ALTER TABLE dbo.Users ADD CONSTRAINT
	FK_Users_ShippingAddresses FOREIGN KEY
	(
	ShippingAddressId
	) REFERENCES dbo.Addresses
	(
	AddressId
	) ON UPDATE  NO ACTION
	 ON DELETE  NO ACTION

GO
ALTER TABLE dbo.Users ADD CONSTRAINT
	FK_Users_BillingAddresse FOREIGN KEY
	(
	BillingAddressId
	) REFERENCES dbo.Addresses
	(
	AddressId
	) ON UPDATE  NO ACTION
	 ON DELETE  NO ACTION

GO
ALTER TABLE dbo.Users SET (LOCK_ESCALATION = TABLE)
GO
COMMIT

First we will update the entity framework model to include a new entity for the Address. Then we will create a partial view that is able to display an address in read-only or edit mode. For knowing if we are in read-only mode we will add a new property (IsReadOnly) to the Address partial class:

using System.ComponentModel.DataAnnotations;

namespace HelloWorld.Code.DataAccess
{
    [MetadataType(typeof(AddressMetaData))]
    public partial class Address
    {
        private bool _isReadOnly = true;
        public bool IsReadOnly
        {
            get { return _isReadOnly; }
            set { _isReadOnly = value; }
        }
    }

    public class AddressMetaData
    {
        [Required(ErrorMessage = "Required")]
        [StringLength(100, MinimumLength = 3, ErrorMessage = "The street must be a string of at least 3 characters and at most 100 characters")]
        [Display(Name = "Street")]
        public string Street { get; set; }

        [StringLength(50, ErrorMessage = "The ZIP code can't have more than 50 characters")]
        [Display(Name = "ZIP Code")]
        public string ZipCode { get; set; }

        [Required(ErrorMessage = "Required")]
        [StringLength(100, MinimumLength = 3, ErrorMessage = "The city must be a string of at least 3 characters and at most 100 characters")]
        [Display(Name = "City")]
        public string City { get; set; }

        [Required(ErrorMessage = "Required")]
        [StringLength(100, MinimumLength = 3, ErrorMessage = "The country must be a string of at least 3 characters and at most 100 characters")]
        [Display(Name = "Country")]
        public string Country { get; set; }
    }
}

We are ready now to create a partial view that will use the Address class as a model. Just like in the case of normal views, the partial views must be stored under the ‘Views’ folder. Because I like to store all my partial views inside a single folder I created a new sub-folder under ‘Views’ called ‘PartialViews’. Then, for adding a partial view, lets make a right click on the ‘PartialViews’ folder in the solution explorer and click ‘Add’ » ‘View’. In the pop-up that opens make sure to tick the check-box ‘Create as a partial view’:

Create a partial view for the address

We will make our partial view strongly typed by specifying, in the first line, the type of the model this partial view works with:

@model HelloWorld.Code.DataAccess.Address

And we will display the address in read-only or edit mode depending on the current value for the model property: @Model.IsReadOnly.

@model HelloWorld.Code.DataAccess.Address

@if(Model == null)
{
    @:None
    <a href="#" class="edit-address">Edit</a>  
}
else if (Model.IsReadOnly)
{
    @Html.LabelFor(model => model.Street)@: <strong>@Model.Street</strong>
    <br/>
    @Html.LabelFor(model => model.ZipCode)@: <strong>@Model.ZipCode</strong>
    <br/>
    @Html.LabelFor(model => model.City)@: <strong>@Model.City</strong>
    <br/>
    @Html.LabelFor(model => model.Country)@: <strong>@Model.Country</strong>
    <br/>
    <a href="#" class="edit-address">Edit</a>          
}
else
{
    @Html.ValidationSummary(true)
 
    @Html.LabelFor(model => model.Street, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Street)
    @Html.ValidationMessageFor(model => model.Street)
    <br/>
    @Html.LabelFor(model => model.ZipCode, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.ZipCode)
    @Html.ValidationMessageFor(model => model.ZipCode)
    <br/>
    @Html.LabelFor(model => model.City, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.City)
    @Html.ValidationMessageFor(model => model.City)
    <br/>
    @Html.LabelFor(model => model.Country, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Country)
    @Html.ValidationMessageFor(model => model.Country)
    <br/>
    <a href="#" class="save-address">Save</a>     
}

Next we will create a controller called UserControlsController and a view called PartialViewExample that will display the name and surname of the currently logged user plus the shipping and billing addresses using the new partial view we just created.

@model HelloWorld.Code.DataAccess.User
@{
    ViewBag.Title = "PartialView Example";
}
<h2>@ViewBag.Title</h2>
<p>You are logged in as <strong>@Model.FirstName @Model.LastName</strong>.</p>
<hr/>
<p>Billing address:</p>
<div id="BillingAddress">
    @Html.Partial("~/Views/PartialViews/AddressPartialView.cshtml", new ViewDataDictionary(Model.BillingAddress))
</div>
<hr/>
<p>Shipping address:</p>
<div id="ShippingAddress">
    @Html.Partial("~/Views/PartialViews/AddressPartialView.cshtml", new ViewDataDictionary(Model.ShippingAddress))
</div>

For inserting a partial view we make use of a helper method Html.Partial and we pass the model to use through a ViewDataDictionary object. We are using the ViewDataDictionary to pass the model to the partial view even if the HtmlHelper class has an overload method for the Partial method that receives as an input the partial view name and the model to pass to the partial view. We do this because the latter method has a curious behavior: in case the passed object is null, it will take the model of the current view and pass it to the partial view instead of passing a null value. So, if we would include the partial view using this method:

@Html.Partial("PartialViews/AddressPartialView", Model.ShippingAddress)

And the current user doesn’t have a shipping address, the Partial method will pass the view’s model which in our care is a User object and we will get a strange run-time error saying that:

The model item passed into the dictionary is of type ‘HelloWorld.Code.DataAccess.User’, but this dictionary requires a model item of type ‘HelloWorld.Code.DataAccess.Address’.

Below is the code for the controller that retrieves the data for the logged user and passes it to the view:

using System.Web.Mvc;
using System.Linq;
using System.Data.Entity;
using HelloWorld.Code.DataAccess;
using HelloWorld.Code.Security;

namespace HelloWorld.Controllers
{
    [Authorize]
    public class UserControlsController : Controller
    {
        public ActionResult PartialViewExample()
        {
            var userId = HttpContext.User.ToCustomPrincipal().CustomIdentity.UserId;
            using (var context = new MvcDemoEntities())
            {
                var user = context.Users.Include(u => u.ShippingAddress).Include(u => u.BillingAddress).First(u => u.UserId == userId);
                return View(user);
            }
        }
    }
}

If you run now the code, you will see that the address partial view was inserted twice in the view.

In practice views are great when updating content via Ajax requests, and this is what we are going to do next. When the user clicks on Edit we will replace the read-only text of the address with the edit-enabled version of the address partial view.

For this we need some JavaScript to make the Ajax calls so we will add the following script inside the view in the scripts section:

@section scripts
{
    <script type="text/javascript">
        $().ready(function () {
            Init();
        });

        function Init() {
            $(".edit-address").click(function (event) {
                event.preventDefault();
                var link = $(this);
                var container = link.parent();

                $.ajax({
                    url: '@Url.Action("EditLoggedUserAddress", "UserControls")',
                    data: { type: container.attr('id') },
                    type: 'GET',
                    timeout: 300000, //5 minutes
                    dataType: 'html',

                    success: function (response) {
                        container.html(response);
                        Init();
                    },
                    error: function () {
                        alert('Error');
                    }
                });
            });
        }
    </script>
}

Note: Sections are not supported inside partial views. That’s why I added the JavaScript code inside the view but a better place to put this script would be in an external JavaScript file.

On the server side, we need a new controller action method that returns the HTML for the partial view when the user clicks on ‘Edit’.

[HttpGet]
public ActionResult EditLoggedUserAddress(string type)
{
	var userId = HttpContext.User.ToCustomPrincipal().CustomIdentity.UserId;
	using (var context = new MvcDemoEntities())
	{
		var user = context.Users.Include(u => u.ShippingAddress).Include(u => u.BillingAddress).First(u => u.UserId == userId);
		var address = string.Compare(type, "ShippingAddress", true) == 0 ? user.ShippingAddress : user.BillingAddress;
		if (address == null)
			address = new Address();

		address.IsReadOnly = false;
		return PartialView("~/Views/PartialViews/AddressPartialView.cshtml", address);
	}
}

The method that renders the partial view is PartialView. We use it to generate the HTML markup only for the AddressPartialView so we can return it to our Ajax caller that will update the HTML content only for that partial view.

Now we need a method for saving the entered data. We will do this again via Ajax calls, adding the following at the end of our Init function inside the view:

$(".save-address").click(function (event) {
	event.preventDefault();
	var link = $(this);
	var container = link.parent();

	$.ajax({
		url: '@Url.Action("SaveLoggedUserAddress", "UserControls")',
		data: container.children("input, textarea, select").serialize() + "&type=" + container.attr('id'),
		type: 'POST',
		timeout: 300000, //5 minutes
		dataType: 'html',

		success: function (response) {
			container.html(response);
			Init();
		},
		error: function () {
			alert('Error');
		}
	});
});

The controller method that will actually save the data is:

[HttpPost]
public ActionResult SaveLoggedUserAddress(Address addressModel, string type)
{
	if (!ModelState.IsValid)
	{
		addressModel.IsReadOnly = false;
		return PartialView("~/Views/PartialViews/AddressPartialView.cshtml", addressModel);
	}

	var userId = HttpContext.User.ToCustomPrincipal().CustomIdentity.UserId;
	var isShippingAddress = System.String.Compare(type, "ShippingAddress", System.StringComparison.OrdinalIgnoreCase) == 0;
	using (var context = new MvcDemoEntities())
	{
		var user = context.Users.Include(u => u.ShippingAddress).Include(u => u.BillingAddress).First(u => u.UserId == userId);
		var address = isShippingAddress ? user.ShippingAddress : user.BillingAddress;
		if (address == null)
		{
			address = addressModel;
			context.AddToAddresses(address);
			if (isShippingAddress)
				user.ShippingAddress = address;
			else
				user.BillingAddress = address;
		}

		address.Street = addressModel.Street;
		address.ZipCode = addressModel.ZipCode;
		address.City = addressModel.City;
		address.Country = addressModel.Country;
		context.SaveChanges();

		address.IsReadOnly = true;
		return PartialView("~/Views/PartialViews/AddressPartialView.cshtml", address);
	}
}

This is it. If you want to download the complete example you can find it here on Codeplex.

3. Custom HtmlHelper methods Go top

Filtered text boxA common way of including dynamically generated HTML markup inside a view is by using the methods defined in the HtmlHelper class. This class contains helper classes that can be used to generate basic HTML elements like text boxes, text areas and check boxes. Apart from the default implementation we can write our own custom helper methods. I explained in detail how this can be done in another post: Custom HtmlHelper for filtering the input in a text box.

4. Templated helpers Go top

[Available soon]

5. Summary Go top

For a comprehensive tutorial check this link: MSDN: Walkthrough: Using Templated Helpers to Display Data in ASP.NET MVC.

Custom HtmlHelper for filtering the input in a text box

The MVC framework contains some helper methods that can be used inside views to programmatically generate basic HTML elements like text boxes, text areas, check boxes and radio buttons. The class that contains those helper methods is called HtmlHelper.

Filtered text box

All the methods of this class return a string that contains the HTML markup to render. Inside a view we can use the HtmlHelper methods by using the Razor syntax @Html. In this way we are accessing the Html property of the view which is an instance of the HtmlHelper class. For example, if we want to render a text box with the name “textboxId” we will write in our view:

@Html.TextBox("textboxId")

The HTML that will be rendered is:

<input id="textboxId" type="text" name="textboxId" value="" />

In addition to the default HtmlHelper methods we can create our own custom methods that generate HTML markup and “attach” them to the HtmlHelper class using extension methods.

As an example we will create a helper method for rendering a filtered text box that allows the user to enter only a given set of characters. If the user enters a character outside the allowed range then, that character will be filtered out and it will not appear in the text box. The control will allow us to specify six types of filters:

  1. AlphabeticCharacter – allows only alphabetic characters from a to z, A to Z and space characters,
  2. Numbers – allows only numbers,
  3. DecimalNumbers – allows numbers and the decimal separator as defined by the current culture info,
  4. DecimalNumbersWithGroupSeparator – allows numbers, the decimal separator and the currency group separator as defined by the current culture info,
  5. CustomValidCharacters – for this filter type we will define a string that contains all the characters that will be allowed in the text box,
  6. CustomInvalidCharacters – for this filter type we will define a string that contains all the characters that aren’t allowed in the text box.

Just to give a famous example, our filtered text box will offer a functionality similar to the FilteredTextBox control contained in the AjaxControlToolkit but the implementation, obviously, has nothing to do with the AjaxControlToolkit.

Now, let’s start writing the code. First we will create a new class called FilteredTextBox that will take care of the HTML rendering. Then we will add extension methods for the HtmlHelper class that will instantiate our new class and provide different filtering types. Because our class must render HTML markup we will have to make it implement the IHtmlString interface. This interface contains only one method ToHtmlString and it is used to tell the MVC framework that the class generates an HTML string that doesn’t need to be encoded. If we don’t specify this interface then, when the output will be rendered in the view, it will be HTML encoded automatically so, in our page, we will see HTML code instead of HTML elements.

To define the six types of supported filters I created an enumeration called FilterType. Here is the complete code of the helper class including the FilterType enumeration:

using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;

namespace Helpers
{
    public class FilteredTextBox : IHtmlString
    {
        #region Filter types

        public enum FilterType
        {
            /// <summary>
            /// Allows only alphabetic characters from a to z and A to Z and space characters.
            /// </summary>
            AlphabeticCharacter,

            /// <summary>
            /// Allows only numbers.
            /// </summary>
            Numbers,

            /// <summary>
            /// Allows numbers and the decimal separator as defined by the current culture info.
            /// </summary>
            DecimalNumbers,

            /// <summary>
            /// Allows numbers, the decimal separator and the currency group separator as defined by the current culture info.
            /// </summary>
            DecimalNumbersWithGroupSeparator,

            /// <summary>
            /// Allows all the characters specified in the ValidCharacters field.
            /// </summary>
            CustomValidCharacters,

            /// <summary>
            /// Allows all the characters except the ones specified in the InvalidCharacters field.
            /// </summary>
            CustomInvalidCharacters
        }

        #endregion

        #region Properties

        public HtmlHelper HtmlHelper { get; set; }

        public FilterType Type { get; set; }

        public string Name { get; set; }

        public object Value { get; set; }

        public string ValidCharacters { get; set; }

        public string InvalidCharacters { get; set; }

        public IDictionary<string, object> HtmlAttributes { get; set; }

        #endregion

        #region Implementation of IHtmlString

        /// <summary>
        /// Returns an HTML-encoded string.
        /// </summary>
        /// <returns>
        /// An HTML-encoded string.
        /// </returns>
        public string ToHtmlString()
        {
            return Render();
        }

        #endregion

        #region Constructors

        internal FilteredTextBox(HtmlHelper htmlHelper, string name, FilterType type, string validCharacters, string invalidCharacters)
        {
            HtmlHelper = htmlHelper;
            Name = name;
            Type = type;
            ValidCharacters = validCharacters;
            InvalidCharacters = invalidCharacters;
        }

        #endregion

        #region Fluent configuration

        /// <summary>
        /// Set the value of the text box.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public FilteredTextBox SetValue(object value)
        {
            Value = value;
            return this;
        }

        /// <summary>
        /// Set the dictionary that contains the HTML attributes to set for the element.
        /// </summary>
        /// <param name="htmlAttributes"></param>
        /// <returns></returns>
        public FilteredTextBox SetHtmlAttributes(IDictionary<string, object> htmlAttributes)
        {
            HtmlAttributes = htmlAttributes;
            return this;
        }

        #endregion

        #region Methods

        public override string ToString()
        {
            return ToHtmlString();
        }

        private string Render()
        {
            var tagBuilder = new TagBuilder("input");

            if (HtmlAttributes != null)
                tagBuilder.MergeAttributes(HtmlAttributes);

            tagBuilder.MergeAttribute("type", "text");

            if (!string.IsNullOrEmpty(Name))
                tagBuilder.MergeAttribute("name", Name, true);

            var value = Value == null ? string.Empty : Value.ToString();
            var regex = string.Empty;
            switch (Type)
            {
                case FilterType.AlphabeticCharacter:
                    regex = "[^a-zA-Z ]";
                    value = Regex.Replace(value, regex, string.Empty);
                    break;
                case FilterType.Numbers:
                    regex = "[^0-9]";
                    value = Regex.Replace(value, regex, string.Empty);
                    break;
                case FilterType.DecimalNumbers:
                    regex = string.Format("[^0-9{0}]", Regex.Escape(System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator));
                    value = Regex.Replace(value, regex, string.Empty);
                    break;
                case FilterType.DecimalNumbersWithGroupSeparator:
                    regex = string.Format("[^0-9{0}{1}]", Regex.Escape(System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator), Regex.Escape(System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberGroupSeparator));
                    value = Regex.Replace(value, regex, string.Empty);
                    break;
                case FilterType.CustomValidCharacters:
                    regex = string.Format("[^{0}]", Regex.Escape(ValidCharacters));
                    value = Regex.Replace(value, regex, string.Empty);
                    break;
                case FilterType.CustomInvalidCharacters:
                    /*
                    regex = string.Join("|", InvalidCharacters.ToCharArray().Select(c => Regex.Escape(c.ToString(CultureInfo.InvariantCulture))));
                    replaced with the next line thanks to Siderite - http://siderite.blogspot.com/
                    */
                    regex = string.Format("[{0}]", Regex.Escape(InvalidCharacters));
                    value = Regex.Replace(value, regex, string.Empty);
                    break;
            }

            tagBuilder.MergeAttribute("data-filter", "1");
            tagBuilder.MergeAttribute("value", value, true);
            tagBuilder.MergeAttribute("data-regex", regex, true);

            if (!string.IsNullOrEmpty(Name))
            {
                // If there are any errors for a named field, we add the css attribute.
                ModelState modelState;
                if (HtmlHelper.ViewData.ModelState.TryGetValue(Name, out modelState))
                {
                    if (modelState.Errors.Count > 0)
                    {
                        tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
                    }
                }
            }

            return tagBuilder.ToString(TagRenderMode.SelfClosing);
        }

        #endregion
    }
}

Because we want to call our class using the usual Razor expression @Html we will have to create extension methods that will invoke our new class:

using System;
using System.Collections.Generic;
using System.Web.Mvc;

namespace Helpers
{
    public static class HtmlExtensions
    {
        #region AlphabeticCharactersTextBox

        /// <summary>
        /// Returns a text input element that allows only alphabetic characters from a to z and A to Z and space characters.
        /// </summary>
        /// <param name="htmlHelper">The HTML helper instance that this method extends.</param>
        /// <param name="name">The name of the form field.</param>
        /// <returns></returns>
        public static FilteredTextBox AlphabeticCharactersTextBox(this HtmlHelper htmlHelper, string name)
        {
            return new FilteredTextBox(htmlHelper, name, FilteredTextBox.FilterType.AlphabeticCharacter, string.Empty, string.Empty);
        }

        /// <summary>
        /// Returns a text input element that allows only alphabetic characters from a to z and A to Z and space characters.
        /// </summary>
        /// <param name="htmlHelper">The HTML helper instance that this method extends.</param>
        /// <param name="name">The name of the form field.</param>
        /// <param name="value">The value of the input element.</param>
        /// <returns></returns>
        public static FilteredTextBox AlphabeticCharactersTextBox(this HtmlHelper htmlHelper, string name, object value)
        {
            return new FilteredTextBox(htmlHelper, name, FilteredTextBox.FilterType.AlphabeticCharacter, string.Empty, string.Empty).SetValue(value);
        }

        /// <summary>
        /// Returns a text input element that allows only alphabetic characters from a to z and A to Z and space characters.
        /// </summary>
        /// <param name="htmlHelper">The HTML helper instance that this method extends.</param>
        /// <param name="name">The name of the form field.</param>
        /// <param name="value">The value of the input element.</param>
        /// <param name="htmlAttributes">An object that contains the HTML attributes to set for the element.</param>
        /// <returns></returns>
        public static FilteredTextBox AlphabeticCharactersTextBox(this HtmlHelper htmlHelper, string name, object value, IDictionary<string, object> htmlAttributes)
        {
            return new FilteredTextBox(htmlHelper, name, FilteredTextBox.FilterType.AlphabeticCharacter, string.Empty, string.Empty).SetValue(value).SetHtmlAttributes(htmlAttributes);
        }

        #endregion

		//The rest of the code was omitted for brevity but you can download the complete source code from Codeplex: 
		//https://mvcfilteredtextbox.codeplex.com/downloads/get/843504
    }
}

This is enough for rendering the HTML code. You can see, in the Render method, that each generated HTML input element contains also an attribute called ‘data-regex’. In the ‘data-regex’ attribute we are storing a regular expression that can select the characters that are not allowed. For example, for the numeric filter, the regular expression that excludes all characters that are not numbers is [^0-9].

Now, the only thing that remains to be done before using the new helper methods is to add some client side code to filter the typed characters. We will write this JavaScript code in an external file and we will include a link to it in our page.

$(document).ready(function () {
    HtmlHelpers.Init();
});

var HtmlHelpers = {
    Init: function () {
        $("input[data-filter='1']").keypress(HtmlHelpers.FilteredTextBox.CheckValue);
    }
};

HtmlHelpers.FilteredTextBox = {
    CheckValue: function (event) {
        var code = event.keyCode || event.which;

        if (event.ctrlKey || event.metaKey || event.altKey || code == 0
            //Text editing characters
            || code ==	33 //Page Up
            || code ==	34 //Page Down
            || code ==	37 //Left arrow
            || code ==	38 //Up arrow
            || code ==	39 //Right arrow
            || code ==	40 //Down arrow            
            || code == 8 //Backspace
            || code == 36 //Home
            || code == 35 //End
            || code == 45 //Insert
            || code == 46 //Delete
            ) {
            return;
        }

        var value = String.fromCharCode(event.which);
        var regex = new RegExp($(this).attr("data-regex"), "");
        
        if (regex.test(value)) {
            event.preventDefault();
        }
    }
};

Now we are able to use our new class inside the view. For example:

@using Helpers

Allows only alphabetic characters
<br/>        
@Html.AlphabeticCharactersTextBox("tbAlphabeticCharacters", string.Empty, new Dictionary<string, object>{{"class", "testCssClass"}})
<br/>
Allows only numbers
<br/>
@Html.NumbersTextBox("tbNumbers").SetValue("123ABC").SetHtmlAttributes(new Dictionary<string, object>{ { "style", "background-color: green;" } })
<br/>
Allows numbers and the decimal separator as defined by the current culture info
<br/>
@Html.DecimalNumbersTextBox("tbDecimalNumbers")
<br/>
Allows numbers, the decimal separator and the currency group separator as defined by the current culture info
<br/>
@Html.DecimalNumbersWithGroupSeparatorTextBox("tbDecimalNumbersWithGroups")
<br/>
Allows the following characters: abcd\
<br/>
@Html.ValidCharactersFilteredTextBox("tbValidCharacters", "abcd\\")
<br/>
Allows all characters except: abcd\
<br/>
@Html.InvalidCharactersFilteredTextBox("tbInvalidCharacters", "abcd\\") 

The result of this view is:

Filtered text boxes example

Note 1: If the FilteredTextBox wouldn’t be implementing the IHtmlString interface than the rendered HTML would be automatically encoded and, when running the site, we would see something like in the following image:

Filtered text boxes example without IHtmlString

Note 2: Another thing to notice is that we created our class so that it allows method chaining:

@Html.NumbersTextBox("tbNumbers").SetValue("123ABC").SetHtmlAttributes(new Dictionary<string, object>{ { "style", "background-color: green;" } })

The method chaining is possible because the SetValue method returns the current object. This improves the readability of the code and is a good idea when writing HtmlHelpers that will be used massively. In the literature this is called a fluent approach and, if you like to read more about this, here are some links on the topic:

Stronly Typed Helpers

As you probably already noticed, there are helper methods that allow us to use lambda expressions to specify both the name of the element and the value to render. For example Html.TextBoxFor provides such a functionality. This kind of HtmlHelper methods are called strongly typed helpers and they follow the naming convention Html.HelperNameFor.

Our next goal is to create some strongly typed helper methods for our FilteredTextBox class. For this we will create new extension methods inside the static class FilteredTextBoxHelper. For example, for the alphabetic characters filtering the strongly typed helper method will be:

/// <summary>
/// Returns a text input element that allows only alphabetic characters from a to z and A to Z and space characters.
/// </summary>
/// <param name="htmlHelper">The HTML helper instance that this method extends.</param>
/// <param name="expression">An expression that identifies the object that contains the properties to render.</param>
/// <returns></returns>
public static FilteredTextBox AlphabeticCharactersTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression)
{
	var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

	var text = metadata.Model;
	var name = ExpressionHelper.GetExpressionText(expression);
	return (new FilteredTextBox(htmlHelper, name, FilteredTextBox.FilterType.AlphabeticCharacter, string.Empty, string.Empty)).SetValue(text);
}

Now we will be able to write in our view something like this:

@Html.AlphabeticCharactersTextBoxFor(model => model.PropertyName)

In a similar way we can write strongly typed extension methods also for the other supported filter types. I will not write here the implementation because it’s similar to the AlphabeticCharactersTextBoxFor method but you can find them, together with the complete source code for the FilteredTextBox and FilteredTextBoxHelper classes on CodePlex: click here to download the source code.

Forms Authentication in ASP.NET MVC 4

Contents:

  1. Introduction
  2. Implement a custom membership provider
  3. Implement a custom role provider
  4. Implement a custom user principal and identity
  5. Implement a custom authorization filter
  6. Summary

1. Introduction Go top

For adding authorization and authentication features to an ASP.NET MVC site we will be using the same approach as for a classic Web Forms project. The classes that stay at the base of the ASP.NET security model can be used in both MVC and Web Forms projects. The authentication happens like in this image:

Authentication

  1. The login page collects the user credentials and then calls the Membership class in order to validate them.
  2. The Membership class uses the web.config to determine what MembershipProvider to use.
  3. In the end the Membership class calls the ValiadateUser method of the membership provider that was determined in step 2. The ValidateUser method verifies if the specified username and password exist and are valid.

The MembershipProvider acts like a mediator between the ASP.NET authentication system and the collection of users. It defines methods for validating the user credentials, for creating new users, modifying the user password and a lot of other user account related operations. For more information here is an introduction to membership in MSDN. So, it provides a way to decouple the data source where the user information is stored (e.g. DB, active directory) from the authentication system so that the authentication will work in the same way no matter where the user information is stored.

Microsoft provides out of the box implementations for ActiveDirectoryMembershipProvider and SqlMembershipProvider but we can also create our own custom implementation by inheriting from the MembershipProvider class and implementing the methods that we need.
Note: if there is a method that you don’t need just leave an empty implementation because it will not be invoked except if you’ll call it explicitly from the code.

In a similar way works also the authorization:

Authorization

  1. We use the AuthorizeAttribute inside the controller classes to mark the action methods that can be invoked only if the user is authenticated and/or has a given role. Then the AuthorizeAttribute uses the Roles class to check if the currently logged user has the required role.
  2. The Roles class uses the web.config to understand what RoleProvider to use.
  3. The RoleProvider is an abstract class that defines the basic methods that all role providers will have. We can use the supplied role providers (e.g. SqlRoleProvider) that are included with the .NET Framework, or we can implement our own custom provider.

2. Implement a custom membership provider Go top

We will continue the demo application we used throughout the previous ASP.NET MVC 4 tutorials and we will add a login page that uses a custom membership provider to authenticate the users. You can download the source code for the demo application (that we are using here as a starting point) from CodePlex.

First we will create a custom membership provider that inherits from the MembershipProvider class.

using System;
using System.Linq;
using System.Web.Security;
using HelloWorld.Code.DataAccess;

namespace HelloWorld.Code.Security
{
    public class CustomMembershipProvider : MembershipProvider
    {
        #region Overrides of MembershipProvider

        /// <summary>
        /// Verifies that the specified user name and password exist in the data source.
        /// </summary>
        /// <returns>
        /// true if the specified username and password are valid; otherwise, false.
        /// </returns>
        /// <param name="username">The name of the user to validate. </param><param name="password">The password for the specified user. </param>
        public override bool ValidateUser(string username, string password)
        {
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
                return false;

            using (var context = new MvcDemoEntities())
            {
                var user = (from u in context.Users
                            where String.Compare(u.Username, username, StringComparison.OrdinalIgnoreCase) == 0
                                  && String.Compare(u.Password, password, StringComparison.OrdinalIgnoreCase) == 0
                                  && !u.Deleted
                            select u).FirstOrDefault();

                return user != null;
            }
        }

        #endregion

        #region Overrides of MembershipProvider that throw NotImplementedException

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override bool EnablePasswordRetrieval
        {
            get { throw new NotImplementedException(); }
        }

        public override bool EnablePasswordReset
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { throw new NotImplementedException(); }
        }

        public override string ApplicationName
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { throw new NotImplementedException(); }
        }

        public override int PasswordAttemptWindow
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresUniqueEmail
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredPasswordLength
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { throw new NotImplementedException(); }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

Note that we provided an implementation only for the ValidateUser method because this is the only method needed to validate the user credentials and, for the moment, we don’t need the other features of the membership provider, like, for example, the ResetPassword method.

Next we will edit the web.config file and enable forms authentication. We will also specify that we will use the CustomMembershipProvider that we just created:

<authentication mode="Forms">
  <forms loginUrl="~/Account/Login" defaultUrl="~/" timeout="20" slidingExpiration="true" />
</authentication>
<membership defaultProvider="CustomMembershipProvider">
  <providers>
	<clear />
	<add name="CustomMembershipProvider"
		 type="HelloWorld.Code.Security.CustomMembershipProvider" />
  </providers>
</membership>

Now we will create a simple log in page that will use the Membership class to check if the user credentials are valid and the FormsAuthentication class to manage the forms authentication inside our site. The implementation is straight forward and I think the code speaks for itself so I’ll copy-paste here the code with minimal annotations.

For displaying the log in page data I created a simple view model that helps with the required data validation:

using System.ComponentModel.DataAnnotations;

namespace HelloWorld.Models
{
    public class Login
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
}

This view model is used inside the view:

@model HelloWorld.Models.Login
@{
    ViewBag.Title = "Log in";
}

<h2>@ViewBag.Title</h2>
<hr/>
@Html.ValidationSummary(true)
<br/>
@using (Html.BeginForm(null, null, new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post))
{
    @Html.LabelFor(m => m.UserName)
    @Html.TextBoxFor(m => m.UserName)
    @Html.ValidationMessageFor(m => m.UserName)
    <br/>
    @Html.LabelFor(m => m.Password)
    @Html.PasswordFor(m => m.Password)
    @Html.ValidationMessageFor(m => m.Password)
    <br/>    
    <label>
        @Html.CheckBoxFor(m => m.RememberMe)
        @Html.LabelFor(m => m.RememberMe)
    </label>
    <br/> 
    <input type="submit" value="Log in" />
}

The controller class that works with this view and authenticates the users is:

using System.Web.Mvc;
using System.Web.Security;
using HelloWorld.Models;

namespace HelloWorld.Controllers
{
    public class AccountController : Controller
    {        
        [HttpGet]
        [AllowAnonymous]
        public ActionResult Login(string returnUrl = "")
        {
            if (User.Identity.IsAuthenticated)
            {
                return LogOut();
            }

            ViewBag.ReturnUrl = returnUrl;
            return View();
        }
    
        [HttpPost]
        [AllowAnonymous]
        public ActionResult Login(Login model, string returnUrl = "")
        {
            if(ModelState.IsValid)
            {
                if (Membership.ValidateUser(model.UserName, model.Password))
                {                   
                    FormsAuthentication.RedirectFromLoginPage(model.UserName, model.RememberMe);
                }

                ModelState.AddModelError("", "Incorrect username and/or password");
            }
                          
            return View(model);
        }

        [HttpPost]
        [AllowAnonymous]
        public ActionResult LogOut()
        {
            FormsAuthentication.SignOut();
            return RedirectToAction("Login", "Account", null);
        }
    }
}

Notice here the use of the AllowAnonymousAttribute. This attribute can be used to indicate that an action method inside a controller or a whole controller doesn’t require user authorization, hence can be accessed by anonymous users. This attribute is used in conjunction with the AuthorizeAttribute that provides a way to restrict the access. Both of this attributes can be set at action method level or at controller level, if we want the same attribute to be applied to all the action methods inside a controller.

Next, we will apply the authorize attribute to our UsersController and HomeController like this:

[Authorize]
public class HomeController : Controller
{
     //code omitted for brevity
}

If we run now the application we will not be able to see the home page until we log in.
Login Page

3. Implement a custom role provider Go top

Now we would like to allow the access to the users controller only for administrators. For doing this we will use the same AuthorizeAttribute that we used before but, this time, we will provide also the name of the user role that is needed to invoke this controller:

[Authorize(Roles = "Administrator")]
public class UsersController : Controller
{
     //code omitted for brevity
}

For this to work we need to add a custom role provider that will be used to return the roles that a user has.

using System;
using System.Collections.Specialized;
using System.Linq;
using System.Data.Entity;
using System.Web;
using System.Web.Caching;
using System.Web.Security;
using HelloWorld.Code.DataAccess;

namespace HelloWorld.Code.Security
{
    public class CustomRoleProvider : RoleProvider
    {
        #region Properties

        private int _cacheTimeoutInMinutes = 30;

        #endregion

        #region Overrides of RoleProvider

        /// <summary>
        /// Initialize values from web.config.
        /// </summary>
        /// <param name="name">The friendly name of the provider.</param>
        /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
        public override void Initialize(string name, NameValueCollection config)
        {
            // Set Properties
            int val;
            if (!string.IsNullOrEmpty(config["cacheTimeoutInMinutes"]) && Int32.TryParse(config["cacheTimeoutInMinutes"], out val))
                _cacheTimeoutInMinutes = val;

            // Call base method
            base.Initialize(name, config);
        }

        /// <summary>
        /// Gets a value indicating whether the specified user is in the specified role for the configured applicationName.
        /// </summary>
        /// <returns>
        /// true if the specified user is in the specified role for the configured applicationName; otherwise, false.
        /// </returns>
        /// <param name="username">The user name to search for.</param><param name="roleName">The role to search in.</param>
        public override bool IsUserInRole(string username, string roleName)
        {
            var userRoles = GetRolesForUser(username);
            return userRoles.Contains(roleName);
        }

        /// <summary>
        /// Gets a list of the roles that a specified user is in for the configured applicationName.
        /// </summary>
        /// <returns>
        /// A string array containing the names of all the roles that the specified user is in for the configured applicationName.
        /// </returns>
        /// <param name="username">The user to return a list of roles for.</param>
        public override string[] GetRolesForUser(string username)
        {
            //Return if the user is not authenticated
            if (!HttpContext.Current.User.Identity.IsAuthenticated) 
                return null;

            //Return if present in Cache
            var cacheKey = string.Format("UserRoles_{0}", username);
            if (HttpRuntime.Cache[cacheKey] != null)
                return (string[])HttpRuntime.Cache[cacheKey];

            //Get the roles from DB
            var userRoles = new string[] { };
            using (var context = new MvcDemoEntities())
            {
                var user = (from u in context.Users.Include(usr => usr.UserRole)
                            where String.Compare(u.Username, username, StringComparison.OrdinalIgnoreCase) == 0
                            select u).FirstOrDefault();

                if (user != null)
                    userRoles = new[]{user.UserRole.UserRoleName};
            }

            //Store in cache
            HttpRuntime.Cache.Insert(cacheKey, userRoles, null, DateTime.Now.AddMinutes(_cacheTimeoutInMinutes), Cache.NoSlidingExpiration);

            // Return
            return userRoles.ToArray();
        }      

        #endregion

        #region Overrides of RoleProvider that throw NotImplementedException

        public override void CreateRole(string roleName)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            throw new NotImplementedException();
        }

        public override bool RoleExists(string roleName)
        {
            throw new NotImplementedException();
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException();
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException();
        }

        public override string[] GetUsersInRole(string roleName)
        {
            throw new NotImplementedException();
        }

        public override string[] GetAllRoles()
        {
            throw new NotImplementedException();
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            throw new NotImplementedException();
        }

        public override string ApplicationName
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        #endregion
    }
}

And, of course, we need to specify that we are using this role provider inside the web.config class:

<roleManager defaultProvider="CustomRoleProvider" enabled="true">
  <providers>
	<clear />
	<add name="CustomRoleProvider" 
		 type="HelloWorld.Code.Security.CustomRoleProvider" 
		 cacheTimeoutInMinutes="30" />
  </providers>
</roleManager>

Now, if we try to access the user list page while being authenticated as users we will be automatically redirected to the log in page.

Next we will add a log out link in the _Layout.chtml file, inside the header div, that will be visible only if the current user is logged in:

<span style="float:right;">@if (HttpContext.Current.User.Identity.IsAuthenticated)
						   {
							   @Html.ActionLink("Log out", "Logout", "Account");
						   }</span>

We are using HttpContext.Current to get all the HTTP-specific information for the current HTTP request. One of these properties is the User property that encapsulates the security information for the current request. The security information related to the current user is accessed through the IPrincipal interface. This interface can be used to check if the current user belong to a given role and gives access to the user’s identity through the IIdentity interface. The IIdentity encapsulates the user data and can be seen as the interface that defines who the user is while the IPrincipal object defines who the current user is and what he is allowed to do.

Sometimes it is useful to extend the default identity and principal objects to include additional user information. This is what we will do in the next section.

4. Implement a custom user principal and identity Go top

First we will define a custom identity class that inherits from IIdentity and receives as an input parameter the default identity object that is created by the forms authentication.

using System;
using System.Security.Principal;
using System.Web.Security;

namespace HelloWorld.Code.Security
{
    /// <summary>
    /// An identity object represents the user on whose behalf the code is running.
    /// </summary>
    [Serializable]
    public class CustomIdentity : IIdentity
    {
        #region Properties

        public IIdentity Identity { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string Email { get; set; }

        public int UserRoleId { get; set; }

        public string UserRoleName { get; set; }

        #endregion

        #region Implementation of IIdentity

        /// <summary>
        /// Gets the name of the current user.
        /// </summary>
        /// <returns>
        /// The name of the user on whose behalf the code is running.
        /// </returns>
        public string Name
        {
            get { return Identity.Name; }
        }

        /// <summary>
        /// Gets the type of authentication used.
        /// </summary>
        /// <returns>
        /// The type of authentication used to identify the user.
        /// </returns>
        public string AuthenticationType
        {
            get { return Identity.AuthenticationType; }
        }

        /// <summary>
        /// Gets a value that indicates whether the user has been authenticated.
        /// </summary>
        /// <returns>
        /// true if the user was authenticated; otherwise, false.
        /// </returns>
        public bool IsAuthenticated { get { return Identity.IsAuthenticated; } }

        #endregion

        #region Constructor

        public CustomIdentity(IIdentity identity)
        {
            Identity = identity;

            var customMembershipUser = (CustomMembershipUser) Membership.GetUser(identity.Name);
            if(customMembershipUser != null)
            {
                FirstName = customMembershipUser.FirstName;
                LastName = customMembershipUser.LastName;
                Email = customMembershipUser.Email;
                UserRoleId = customMembershipUser.UserRoleId;
                UserRoleName = customMembershipUser.UserRoleName;
            }
        }
 
        #endregion
    }
}

The constructor uses the name of the default identity and the current membership provider to get the user data. We will modify our custom membership provider by implementing the methods GetUser and Initialize. We need to implement the Initialize method because we will use caching to remember the user information between post backs and we want to be able to set the caching time in the web.config like this:

<membership defaultProvider="CustomMembershipProvider">
  <providers>
	<clear />
	<add name="CustomMembershipProvider"
		 type="HelloWorld.Code.Security.CustomMembershipProvider" 
		 cacheTimeoutInMinutes="30" />
  </providers>
</membership>

In the initialize method we will read the cacheTimeoutInMinutes value as it is defined in the web.config.

private int _cacheTimeoutInMinutes = 30;

/// <summary>
/// Initialize values from web.config.
/// </summary>
/// <param name="name">The friendly name of the provider.</param>
/// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
public override void Initialize(string name, NameValueCollection config)
{
	// Set Properties
	int val;
	if (!string.IsNullOrEmpty(config["cacheTimeoutInMinutes"]) && Int32.TryParse(config["cacheTimeoutInMinutes"], out val))
		_cacheTimeoutInMinutes = val;

	// Call base method
	base.Initialize(name, config);
}

Than we implement the method for retrieving the user data. Because we want to return more user information than we can store in the default MembershipUser, that is the default return type of the GetUser method, we will first create a custom implementation for the MembershipUser.

using System;
using System.Web.Security;
using HelloWorld.Code.DataAccess;

namespace HelloWorld.Code.Security
{
    public class CustomMembershipUser : MembershipUser
    {
        #region Properties

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public int UserRoleId { get; set; }

        public string UserRoleName { get; set; }

        #endregion

        public CustomMembershipUser(User user)
            : base("CustomMembershipProvider", user.Username, user.UserId, user.Email, string.Empty, string.Empty, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now)
        {
            FirstName = user.FirstName;
            LastName = user.LastName;
            UserRoleId = user.UserRoleId;
            UserRoleName = user.UserRole.UserRoleName;
        }
    }
}

Now we can implement the method for retrieving the user data inside our custom membership provider:

/// <summary>
/// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
/// </summary>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the specified user's information from the data source.
/// </returns>
/// <param name="username">The name of the user to get information for. </param><param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. </param>
public override MembershipUser GetUser(string username, bool userIsOnline)
{
	var cacheKey = string.Format("UserData_{0}", username);
	if (HttpRuntime.Cache[cacheKey] != null)
		return (CustomMembershipUser)HttpRuntime.Cache[cacheKey];
	
	using (var context = new MvcDemoEntities())
	{
		var user = (from u in context.Users.Include(usr => usr.UserRole)
					where String.Compare(u.Username, username, StringComparison.OrdinalIgnoreCase) == 0
						  && !u.Deleted
					select u).FirstOrDefault();

		if (user == null)
			return null;

		var membershipUser = new CustomMembershipUser(user);

		//Store in cache
		HttpRuntime.Cache.Insert(cacheKey, membershipUser, null, DateTime.Now.AddMinutes(_cacheTimeoutInMinutes), Cache.NoSlidingExpiration);

		return membershipUser;
	}            
}

We will also create a custom principal that works with our new CustomIdentity class.

using System;
using System.Security.Principal;

namespace HelloWorld.Code.Security
{
    [Serializable]
    public class CustomPrincipal : IPrincipal
    {
        #region Implementation of IPrincipal

        /// <summary>
        /// Determines whether the current principal belongs to the specified role.
        /// </summary>
        /// <returns>
        /// true if the current principal is a member of the specified role; otherwise, false.
        /// </returns>
        /// <param name="role">The name of the role for which to check membership. </param>
        public bool IsInRole(string role)
        {
            return Identity is CustomIdentity &&
                   string.Compare(role, ((CustomIdentity) Identity).UserRoleName, StringComparison.CurrentCultureIgnoreCase) == 0;
        }

        /// <summary>
        /// Gets the identity of the current principal.
        /// </summary>
        /// <returns>
        /// The <see cref="T:System.Security.Principal.IIdentity"/> object associated with the current principal.
        /// </returns>
        public IIdentity Identity { get; private set; }

        #endregion

        public CustomIdentity CustomIdentity { get { return (CustomIdentity)Identity; } set { Identity = value; } }

        public CustomPrincipal(CustomIdentity identity)
        {
            Identity = identity;
        }
    }
}

The only thing left is to replace the default HttpContext.Current.User with our new CustomProvider. We will do this by adding the method Application_PostAuthenticateRequest inside Global.asax:

protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
	if (Request.IsAuthenticated)
	{
		var identity = new CustomIdentity(HttpContext.Current.User.Identity);
		var principal = new CustomPrincipal(identity);
		HttpContext.Current.User = principal;
	}
}

Now we are able to use our new custom identity and principal. For example we will modify the _Layout.chtml file to display more information about the currently logged user in the header.

<div id="header">
	<i>=(^.^)= @ViewBag.Title</i>
	<span style="float:right; font-size: small;">@if (HttpContext.Current.User.Identity.IsAuthenticated)
							   {
								   var identity = ((CustomPrincipal)HttpContext.Current.User).CustomIdentity;
								   @Html.Label(string.Format("Welcome {0} {1}, you are logged as {2}", identity.FirstName, identity.LastName, identity.UserRoleName))
								   @Html.ActionLink("Log out", "Logout", "Account")
							   }</span>
</div>

And, the final touch, we create an extension method that converts an IPrincipal object to a CustomIdentity object. We do this just because the code will be more elegant:

using System.Security.Principal;

namespace HelloWorld.Code.Security
{
    public static class SecurityExtentions
    {
        public static CustomPrincipal ToCustomPrincipal(this IPrincipal principal)
        {
            return (CustomPrincipal) principal;
        }
    }
}

Now we can get the custom identity like this:

var identity = HttpContext.Current.User.ToCustomPrincipal().CustomIdentity;

The final result will be like this:
Display the user data

5. Implement a custom authorization filter Go top

It is possible to create custom attributes that, when applied to a controller class and/or a controller action method will perform an additional logic before or after the action method is executed. These attributes must inherit from the FilterAttribute class and must implement at least one of the following interfaces:

  1. IAuthorizationFilter
  2. IActionFilter
  3. IResultFilter
  4. IExceptionFilter

If an action method has more than one filter then they will be executed in the same order as in the list above, starting with the authorization filters and continuing with the action, result and, in the end, exception filters. You can find more information about action filtering and about the order in which the action filters are executed on MSDN.

As a first example, we will create a custom attribute that inherits from the System.Web.Mvc.AuthorizeAttribute and will allow us to pass the allowed user roles as a list of UserRole enumerations instead of passing a string containing the comma separated list of allowed user roles.

using System.Linq;

namespace HelloWorld.Code.Security
{
    public class UserRoleAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
    {
        public UserRoleAuthorizeAttribute(){}

        public UserRoleAuthorizeAttribute(params UserRole[] roles)
        {
            Roles = string.Join(",", roles.Select(r => r.ToString()));
        }
    }

    public enum UserRole
    {
        Administrator = 1,
        User = 2
    }
}

Usage example:

[UserRoleAuthorize(UserRole.Administrator, UserRole.User)]

As a second example we will create a new custom filter that will allow the execution of an action method only between two given hours. For example, we want to allow the user add, edit and delete functionality only between 9 in the morning and 19 in the evening. If we are outside this time frame then we will redirect the user to the home page. Again we will be inheriting the System.Web.Mvc.AuthorizeAttribute because it is easier to start from it than from the basic FilterAttribute class.

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace HelloWorld.Code.Security
{
    public class TimeAuthorizeAttribute : AuthorizeAttribute
    {
        public int StartTime { get; set; }
        public int EndTime { get; set; }

        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (DateTime.Now.Hour < StartTime)
                return false;

            if (EndTime <= DateTime.Now.Hour)
                return false;

            return true;
        }

        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" } });
        }
    }
}

All we did was to override the method that decides if the current user is authorized AuthorizeCore. In case the user is not authorized we will redirect him to the home page. We accomplish this by overriding the method HandleUnauthorizedRequest.

6. Summary Go top

In this post we had a look on how to implement custom authentication in an ASP.NET MVC 4 application. You can download the source code that contains all the points discussed here from Codeplex.

Add, edit, delete list elements

Contents:

  1. Introduction
  2. Add CRUD operations to the generic repository
  3. Define the user model
  4. Add a view for adding and modifying an user
  5. Implement the delete feature
  6. Summary

1. Introduction Go top

We will continue the solution built in the previous post Paging, sorting and filtering a list in ASP.NET MVC 4 and implement new features for adding, modifying and removing a list element. You can download the source code for the previous solution from Codeplex and use it as the starting point for implementing the features discussed here. The final result will look like this:
List-edit result
The main steps are:

  • Extend the generic repository to support CRUD (Create Read Update Delete) operations.
  • Create a view model starting from the User class that was generated by EF that will define the validation rules and will serve as the model for our user edit view.
  • Create controller action methods to support adding and modifying a user and the associated view that knows how to render a User view model.
  • Implement the delete action inside the users list.

2. Add CRUD operations to the generic repository Go top

First we will enhance the generic repository we created in the previous post to support adding, modifying and deleting an entity. We will also add a method for retrieving an entity based on its primary key supposing that the primary key is not composite (is based only on one table column).

Note: None of these methods will call save changes on the context because it will be the invoked from the unit of work.

/// <summary>
/// Retrieve an entity from the repository based on the unique identifier.
/// </summary>
/// <param name="id">The unique identifier of the entity.</param>
/// <returns></returns>
public T GetById(object id)
{
	var entityKey = new EntityKey(EntitySetName, PrimaryKeyName, id);
	object entity;
	if (Context.TryGetObjectByKey(entityKey, out entity))
		return (T)entity;
	
	return null;
}

/// <summary>
/// Add a new item to the repository.
/// </summary>
/// <param name="entity">The element to add to the repository.</param>
/// <returns></returns>
public void Insert(T entity)
{      
	ObjectSet.AddObject(entity);
}

/// <summary>
/// Updates an item in the repository.
/// </summary>
/// <param name="entity">The element to update.</param>
/// <returns></returns>
public void Update(T entity)
{
	if (!IsAttached(entity))
		Context.AttachTo(EntitySetName, entity);

	Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}

/// <summary>
/// Deletes an item from the repository.
/// </summary>
/// <param name="entity">The item to be deleted.</param>
/// <returns></returns>
public void Delete(T entity)
{
	if (!IsAttached(entity))
		Context.AttachTo(EntitySetName, entity);

	Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
}

/// <summary>
/// Deletes an item from the repository.
/// </summary>
/// <param name="id">The unique identifier of the entity to be deleted.</param>
/// <returns></returns>
public void Delete(object id)
{
	var entity = GetById(id);
	if(entity == null)
		throw new Exception("Entity not found.");
	Delete(entity);
}

/// <summary>
/// Returns true if the entity is attached to the current context.
/// </summary>
/// <param name="entity"></param>
protected bool IsAttached( T entity)
{
	ObjectStateEntry entry;
	Context.ObjectStateManager.TryGetObjectStateEntry(Context.CreateEntityKey(EntitySetName, entity), out entry);

	return !(entry == null || entry.State == EntityState.Detached);
}		

3. Define the user model Go top

Next, we will create the model by adding a partial class for the EF User class. Inside this partial class we define the model validation rules:

  • using attributes for basic field validation rules and
  • implementing the IValidatableObject interface to check if the username is unique among all users.

You can find more details in a previous post that was exclusively dedicated to data validation.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using HelloWorld.Code.Util;

namespace HelloWorld.Code.DataAccess
{
    [MetadataType(typeof(UserMetaData))]
    public partial class User : IValidatableObject
    {
        /// <summary>
        /// Helper property used to store the URL referrer of the current page. Needed for the 'Go back to list' functionality.
        /// </summary>
        public string UrlReferrer { get; set; }

        /// <summary>
        /// Get a list that contains the available user roles.
        /// </summary>
        public IEnumerable<SelectListItem> UserRolesList
        {
            get
            {
                return (new[]
                            {
                                new SelectListItem
                                    {
                                        Selected = (UserRoleId == 0),
                                        Text = "Select...",
                                        Value = string.Empty
                                    }
                            }).Union(CacheManager.GetUserRoles(HttpContext.Current.Request.HttpMethod == "POST").
                                         Select(c => new SelectListItem
                                         {
                                             Selected = (c.UserRoleId == UserRoleId),
                                             Text = c.UserRoleName,
                                             Value = c.UserRoleId.ToString(CultureInfo.InvariantCulture)
                                         }));
            }
        }

        /// <summary>
        /// Saves the model using the repository and unit of work.
        /// </summary>
        public void Save()
        {
            using (var unitOfWork = new UnitOfWork())
            {
                var repository = unitOfWork.GetUserRepository;

                if (UserId == 0)
                    repository.Insert(this);
                else
                    repository.Update(this);

                unitOfWork.Save();                
            }
        }

        /// <summary>
        /// Validates that the username is unique among all users.
        /// </summary>
        /// <param name="validationContext"></param>
        /// <returns></returns>
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            using (var unitOfWork = new UnitOfWork())
            {
                var repository = unitOfWork.GetUserRepository;
                var existingUser = repository.Search(u => u.UserId != UserId &&
                        string.Compare(u.Username, Username, StringComparison.CurrentCultureIgnoreCase) == 0, null, null);
                
                if(existingUser.Count > 0)
                    yield return new ValidationResult("Username already taken. Please choose another username.", new[] { "Username" });
            }
        }
    }

    public class UserMetaData
    {
        [Required(ErrorMessage = "Required")]
        [StringLength(100, MinimumLength = 3, ErrorMessage = "The first name must be a string of at least 3 characters and at most 100 characters")]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [Required(ErrorMessage = "Required")]
        [StringLength(100, MinimumLength = 3, ErrorMessage = "The last name must be a string of at least 3 characters and at most 100 characters")]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }

        [Required(ErrorMessage = "Required")]
        [StringLength(100, ErrorMessage = "The e-mail can't have more than 100 characters")]
        [RegularExpression(@"^\w+([-+.]*[\w-]+)*@(\w+([-.]?\w+)){1,}\.\w{2,4}$", ErrorMessage = "Invalid e-mail")]
        [Display(Name = "E-mail")]
        public string Email { get; set; }

        [Required(ErrorMessage = "Required")]
        [StringLength(50, MinimumLength = 3, ErrorMessage = "The username must be a string of at least 3 characters and at most 50 characters")]
        public string Username { get; set; }

        [Required(ErrorMessage = "Required")]
        [StringLength(50, MinimumLength = 3, ErrorMessage = "The password must be a string of at least 3 characters and at most 50 characters")]
        public string Password { get; set; }

        [Required(ErrorMessage = "Required")]
        [Range(1, 2, ErrorMessage = "Required")]
        [Display(Name = "User role")]
        public int UserRoleId { get; set; }
    }
}

4. Add a view for adding and modifying an user Go top

For adding and editing an user we will add action methods inside the UsersController and a view for defining the user interface.

[HttpGet]
public ActionResult Add()
{
	ViewBag.IsNewUser = true;
	return View("User", new User {UrlReferrer = Request.UrlReferrer == null ? string.Empty : Request.UrlReferrer.AbsoluteUri});
}

[HttpPost]
public ActionResult Add(User model)
{
	ViewBag.IsNewUser = true;
	return Save(model);
}

[HttpGet]
public ActionResult Edit(int id = 0)
{
	ViewBag.IsNewUser = false;
	var user = new UnitOfWork().GetUserRepository.GetById(id);
	if (user == null)
	{
		ViewBag.Error = "User not found";
		user = new User();
	}
	user.UrlReferrer = Request.UrlReferrer == null ? string.Empty : Request.UrlReferrer.AbsoluteUri;
	
	return View("User", user);
}

[HttpPost]
public ActionResult Edit(User model)
{
	ViewBag.IsNewUser = false;
	return Save(model);
}

protected ActionResult Save(User model)
{
	if(ModelState.IsValid)
	{
		try
		{
			model.Save();
			ViewBag.Message = "The user was saved successfully";
		}
		catch (Exception exp)
		{                    
			ViewBag.Error = "There was an unexpected error while saving the user.";
		}
	}
	return View("User", model);
}

The last thing needed is a view that supports both cases: add new and edit user.

@model HelloWorld.Code.DataAccess.User
@{
    ViewBag.Title = ViewBag.IsNewUser ? "Add a new user" : "Edit user";
}

<h2>@ViewBag.Title</h2>
<hr/>
@if (ViewBag.Error != null && !string.IsNullOrEmpty(ViewBag.Error))
{
    <p style="color: red">@ViewBag.Error</p>
}
else if (ViewBag.Message != null && !string.IsNullOrEmpty(ViewBag.Message))
{
    <p style="color: green">@ViewBag.Message</p>
}
else
{            
    using (Html.BeginForm(ViewBag.IsNewUser ? "Add" : "Edit", "Users", FormMethod.Post))
    {       
        @Html.HiddenFor(model => model.UserId)
        @Html.HiddenFor(model => model.UrlReferrer)        
    
        @Html.LabelFor(model => model.FirstName, new { @class = "control-label" })
        @Html.TextBoxFor(model => model.FirstName)
        @Html.ValidationMessageFor(model => model.FirstName)
        <br/>
        @Html.LabelFor(model => model.LastName, new { @class = "control-label" })
        @Html.TextBoxFor(model => model.LastName)
        @Html.ValidationMessageFor(model => model.LastName)
        <br/>
        @Html.LabelFor(model => model.Email, new { @class = "control-label" })
        @Html.TextBoxFor(model => model.Email)
        @Html.ValidationMessageFor(model => model.Email)
        <br/>
        @Html.LabelFor(model => model.Username, new { @class = "control-label" })
        @Html.TextBoxFor(model => model.Username)
        @Html.ValidationMessageFor(model => model.Username)
        <br/>
        @Html.LabelFor(model => model.Password, new { @class = "control-label" })
        @Html.TextBoxFor(model => model.Password)
        @Html.ValidationMessageFor(model => model.Password)
        <br/>
        @Html.LabelFor(model => model.UserRoleId, new { @class = "control-label" })
        @Html.DropDownListFor(model => model.UserRoleId, Model.UserRolesList)
        @Html.ValidationMessageFor(model => model.UserRoleId)
        <br/>
        <input type="submit" name="Save" value="Save" />
    }
}
<br/>
@if (!string.IsNullOrEmpty(Model.UrlReferrer))
{
    <a href="@Model.UrlReferrer">Go back</a>
}

5. Implement the delete feature Go top

For deleting the user we will modify the index action method associated to the post HTTP method so that it accepts an optional parameter “hfDeleteId ” that, when provided, will contain the id of the user to delete.

[HttpGet]
public ActionResult Index(UsersList model, int page = 1, string sort = "", string direction = "")
{
	model.SetParameters(page, sort, direction, Request.Params);
	return View(model);
}

[HttpPost]
public ActionResult Index(UsersList model, int page = 1, string sort = "", string direction = "", int hfDeleteId = 0)
{           
	if (hfDeleteId > 0)
	{
		#region Delete

		using (var unitOfWork = new UnitOfWork())
		{
			var repository = unitOfWork.GetUserRepository;
			var user = repository.GetById(hfDeleteId);
			if (user != null)
			{
				try
				{
					unitOfWork.GetUserRepository.Delete(user);
					unitOfWork.Save();
					ViewBag.DeleteMessage = "The user was deleted";
					ViewBag.Deleted = true;
				}
				catch
				{
					ViewBag.DeleteMessage = "An unexpected error occured";
					ViewBag.Deleted = false;
				}
			}
			else
			{
				ViewBag.DeleteMessage = "The user to delete wasn't found";
				ViewBag.Deleted = false;
			}
		}

		#endregion

		model.SetParameters(page, sort, direction, Request.Params);
		return View(model);
	}
	
	//We do this redirect in order to preserve the search parameters in the URL after the user presses the search button.
	model.SetParameters(page, sort, direction, Request.Params);
	return RedirectToAction("Index", (new UrlHelper(ControllerContext.RequestContext)).GetRouteValueDictionaryForList(model));
}

The view for the users list was also changed to render two links for each row allowing us to edit and/or delete that user.

@using System.ComponentModel
@using HelloWorld.Code.Util
@model HelloWorld.Models.UsersList
@{
    ViewBag.Title = "Users list";
}
@using (Html.BeginForm("Index", "Users", FormMethod.Post, new {id = "frmSearch"}))
{            
    @Html.LabelFor(model => model.FirstName) 
    @Html.TextBoxFor(model => model.FirstName)
    <br/> 
    @Html.LabelFor(model => model.LastName) 
    @Html.TextBoxFor(model => model.LastName)   
    <input type="submit" name="Search" value="Search"/>   
}
@using (Html.BeginForm("Index", "Users", FormMethod.Post, new {id = "frmDelete"}))
{            
    <input type="hidden" id="hfDeleteId" name="hfDeleteId"/>
    @Html.HiddenFor(model => model.FirstName)
    @Html.HiddenFor(model => model.LastName)                                             
}
@if (!string.IsNullOrEmpty(ViewBag.DeleteMessage))
{
    <p style="color: @(ViewBag.Deleted ? "green" : "red")">@ViewBag.DeleteMessage</p>
}
@if (!Model.PagedList.CurrentPage.Any())
{
    <p>No users were found</p>
}
else
{
    <table>
        <thead>
            <tr>
                <th>@Html.ActionLink("First name", "Index", "Users",
                                    Url.GetRouteValueDictionaryForList(
                                        Model,
                                        sortColumn: "FirstName",
                                        direction: string.Compare(Model.SortColumn, "FirstName",
                                        StringComparison.CurrentCultureIgnoreCase) != 0 ? "asc" : Model.SortDirection == ListSortDirection.Ascending ? "desc" : "asc"), null)</th>
                <th>@Html.ActionLink("Last name", "Index", "Users",
                                    Url.GetRouteValueDictionaryForList(
                                        Model,
                                        sortColumn: "LastName",
                                        direction: string.Compare(Model.SortColumn, "LastName", StringComparison.CurrentCultureIgnoreCase) != 0 ? "asc" : Model.SortDirection == ListSortDirection.Ascending ? "desc" : "asc"), null)</th>
                <th>E-mail</th>
                <th>Role</th>
                <th>Actions</th>
            </tr>
        </thead>
        @foreach (var user in Model.PagedList.CurrentPage)
        {
            <tr>
                <td>@user.FirstName</td>
                <td>@user.LastName</td>
                <td>@user.Email</td>
                <td>@user.UserRole.UserRoleName</td>
                <td>
                    @Html.ActionLink("Edit", "Edit", "Users", new RouteValueDictionary { { "id", @user.UserId } }, null) | 
                    <a href="javascript:;" class="delete" data-id="@user.UserId">Delete</a>                   
                </td>
            </tr>
        } 
    </table>
@Html.PagedListPager(Url, new PagerHtmlRenderer(
                                                currentPageNumber: Model.PagedList.CurrentPageNumber,
                                                pageSize: Model.PagedList.PageSize,
                                                totalNumberOfItems: Model.PagedList.TotalNumberOfItems,
                                                actionName: "Index",
                                                controllerName: "Users",
                                                routeValues: Url.GetRouteValueDictionaryForList(Model),
                                                pageRouteValueName: "page"))
    
}
<br />
<br/>
@Html.ActionLink("Add a new user", "Add", "Users")

@section scripts
{
    <script type="text/javascript">
        $().ready(function () {
            $('.delete').click(function (event) {
                event.preventDefault();
                $("#hfDeleteId").val($(this).attr("data-id"));
                document.forms["frmDelete"].submit();
            });
        });
    </script>    
}

Note that we restricted the deletion only for the HTML POST method, using the [HttpPost] attribute in the controller. Although it is possible to delete an item using a GET method this is not recommended since it might generate a security hole. For example let’s suppose our delete link would not execute any JavaScript to post the form but would only point to an URL that contains the id of the user to delete. Something like this:

<a href="http://localhost:12317/Users/Index?delete=10">Delete</a>   

And let’s also suppose that the controller action method that handles the HTTP GET checks if a delete parameter is passed in the query string and, if so, it deletes the user with that id. Now, when spiders/bots/crawlers will parse our page and try to follow the links they will trigger a delete. So when the site will be online and the Googlebot will try to index our pages it will follow the links it finds and automatically delete our data.

Even if you restrict that link only for authenticated users you will still have a security hole. For example, a user that is logged in could receive an malicious e-mail that contains an image that has the src attribute set to this URL:

<img src="http://localhost:12317/Users/Index?delete=10" />

If the user will allow images from his e-mail client then a delete will happen without the user being aware of that.

As a general rule, remember to use:

  • GET – for safe operations that only retrieve data without changing the state of the resource on the server,
  • POST – for operations that change the state of the resource,

Here is an funny post about Why learning HTTP does matter that summaries very nicely the unwanted behavior we might create using the wrong HTTP methods.

6. Summary Go top

This post is part of a series of demos about starting to work with ASP.NET MVC 4. Until now we have created a simple list that supports paging, sorting and filtering, and we implemented the add, modify and delete list item features as described in this post. You can download the complete source code that includes both the list and the edit from Codeplex.

Paging, sorting and filtering a list in ASP.NET MVC 4

Contents:

  1. Introduction
  2. Creating a list
  3. The repository pattern
  4. The unit of work pattern
  5. Adding a pager to the list
  6. Allow sorting on the list
  7. Add list filters
  8. Create a base view model for lists that support sorting, filtering and paging
  9. Summary

1. Introduction Go top

Starting from the solution created in the previous post Data validation, we will build from scratch a new page that displays a list of users and gives the possibility to filter, page and sort the list. The final result will look like this:

MVC Course 3 - Final result

We will be building the examples in this post over this solution, so we will have some predefined CSS styles already in place but you can start as well from a completely new solution.

2. Creating a list Go top

DB structure
Let’s start with the necessary DB tables: [dbo].[Users] and [dbo].[UserRoles]. Below is the SQL script for creating and populating these two tables with some default data.

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[UserRoles](
	[UserRoleId] [int] NOT NULL,
	[UserRoleName] [varchar](50) NOT NULL,
 CONSTRAINT [PK_UserRoles] PRIMARY KEY CLUSTERED
(
	[UserRoleId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

INSERT INTO [dbo].[UserRoles]([UserRoleId],[UserRoleName]) VALUES (1,'Administrator')
INSERT INTO [dbo].[UserRoles]([UserRoleId],[UserRoleName]) VALUES (2,'User')
GO

CREATE TABLE [dbo].[Users](
	[UserId] [int] IDENTITY(1,1) NOT NULL,
	[FirstName] [varchar](100) NOT NULL,
	[LastName] [varchar](100) NOT NULL,
	[Email] [varchar](100) NULL,
	[Username] [varchar](50) NOT NULL,
	[Password] [varchar](50) NOT NULL,
	[UserRoleId] [int] NOT NULL,
	[Deleted] [bit] NOT NULL,
 CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
(
	[UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE UNIQUE NONCLUSTERED INDEX [IDX_Username] ON [dbo].[Users]
(
	[Username] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Users]  WITH CHECK ADD  CONSTRAINT [FK_Users_UserRoles] FOREIGN KEY([UserRoleId])
REFERENCES [dbo].[UserRoles] ([UserRoleId])
GO

ALTER TABLE [dbo].[Users] CHECK CONSTRAINT [FK_Users_UserRoles]
GO

INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('John','Dow','administrator@mailinator.com','admin','password',1,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Bela','Lugosi','bela.lugosi@mailinator.com','bela.lugosi','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('David','Copperfield','david.copperfield@mailinator.com','david.copperfield','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Jennifer','Jones','jennifer.jones@mailinator.com','jennifer.jones','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Sophia Loren','Loren','sophia.loren@mailinator.com','sophia.loren','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Woody','Allen','woody.allen@mailinator.com','woody.allen','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Stanley','Donwood','stanley.donwood@mailinator.com','stanley.donwood','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Marilyn','Monroe','marilyn.monroe@mailinator.com','marilyn.monroe','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Jamie','Foxx','jamie.foxx@mailinator.com','jamie.foxx','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('John','Ford','john.ford@mailinator.com','john.ford','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Diane','Keaton','diane.keaton@mailinator.com','diane.keaton','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Angelina','Jolie','angelina.jolie@mailinator.com','angelina.jolie','password',2,0)
GO
INSERT INTO [dbo].[Users] ([FirstName],[LastName],[Email],[Username],[Password],[UserRoleId],[Deleted])
VALUES ('Brad','Pitt','brad.pitt@mailinator.com','brad.pitt','password',2,0)
GO

Next we will update the edmx model to include users and user roles and then we shall add a new controller called UsersController. By default our controller will have an action method called Index. Let’s add a view for the Index method and display the list of users that aren’t deleted. First we will fetch the list from the DB inside the controller and pass it to the view.

Note: For the moment, I will concentrate mainly on the user interface and I will put all the server side logic intro the controller but lately we will refactor our application’s architecture using a view model and applying the repository and unit of work patterns.

public ActionResult Index()
{
	using (var context = new MvcDemoEntities())
	{
		var list = context.Users.Include(u => u.UserRole).Where(u => !u.Deleted).ToList();
		return View(list);
	}
}

In the view we will iterate through the collection of users and render one row for each user:

@model IEnumerable
@{
    ViewBag.Title = "Users list";
}
@if (!Model.Any())
{
    <div>No users were found.</div>
}
else
{
    <table>
        <thead>
            <tr>
                <th>First Name</th>
                <th>Last Name</th>
                <th>E-mail</th>
                <th>Role</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var user in Model) {
                <tr>
                    <td>@user.FirstName</td>
                    <td>@user.LastName</td>
                    <td>@user.Email</td>
                    <td>@user.UserRole.UserRoleName</td>
                </tr>
            }
        </tbody>
    </table>
}

If you run now the site, you will see a table with all the users in the DB.

3. The repository pattern Go top

As defined by Martin Fowler:

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.

So, the repository pattern is used to create an abstraction between the business layer and the data access layer so that the business layer doesn’t have to know anything about the underlying data access techniques be it static files, external services, ADO.NET, Entity Framework or NHibernate. The main advantages that come from this abstraction are:

  • Helps to separate the application layers providing a simplified model for obtaining/persisting data.
  • Makes it easier to implement unit testing because it provides an easy way of defining test repositories that create in-memory dummy collections and use them for testing instead of accessing the real data source.

You can find a complete explanation of the Repository Pattern on MSDN.

Below is an interesting recommendation taken from the book “Domain-Driven Design: Tackling Complexity in the Heart of Software” that I added here because I think it’s a good recommendation:

Leave transaction control to the client. Although the REPOSITORY will insert into and delete from the database, it will ordinarily not commit anything. It is tempting to commit after saving, for example, but the client presumably has the context to correctly initiate and commit units of work. Transaction management will be simpler if the REPOSITORY keeps its hands off.

After this brief introduction, let’s use the repository pattern in our example. For the moment, in order to keep things simple, we will define only a data retrieval method and later, in the next post, we will add data persistence methods as we will define the add/edit/delete user functionality.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace HelloWorld.Code.DataAccess
{
    /// <summary>
    /// Base repository class
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IRepository where T : class
    {
        /// <summary>
        /// Return a list.
        /// </summary>
        /// <param name="filters">A lambda expression used to filter the result.</param>
        /// <param name="sorting">The sort expression, example: ColumnName desc</param>
        /// <param name="includeList">The list of related entities to load.</param>
        /// <returns></returns>
        IList Search(Expression<Func<T, bool>> filters, string sorting, List includeList);
    }
}

Before building the basic repository class, we will add the System.Linq.Dynamic library using NuGet. We need it because we will create dynamic Linq queries inside our repositiries starting from the input parameters. Below is the code for the default repository class:

using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using System.Linq.Dynamic;

namespace HelloWorld.Code.DataAccess
{
    public class Repository<T> : IRepository<T> where T : class
    {
        internal MvcDemoEntities Context;
        public Repository(MvcDemoEntities context)
        {
            Context = context;
        }

        public IList<T> Search(Expression<Func<T, bool>> filters, string sorting, List<string> includeList)
        {
            using (var context = new MvcDemoEntities())
            {
                return GetQuery(context, filters, sorting, includeList).ToList();
            }
        }

        /// <summary>
        /// Method used to build the query that will reflect the filter conditions and the sort expression.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="filters"></param>
        /// <param name="sorting"></param>
        /// <param name="includeList"></param>
        /// <returns></returns>
        protected IQueryable<T> GetQuery(ObjectContext context, Expression<Func<T, bool>> filters, string sorting, List<string> includeList)
        {
            var objectSet = context.CreateObjectSet<T>();
            if (string.IsNullOrEmpty(sorting))
                sorting = objectSet.EntitySet.ElementType.KeyMembers.First().Name; //consider the PK as the default sort column

            var objectQuery = (ObjectQuery<T>)objectSet;
            if (includeList != null)
            {
                foreach (var include in includeList)
                {
                    objectQuery = objectQuery.Include(include);
                }
            }

            var query = objectQuery.Where(filters == null ? t => 1 == 1 : filters);
            query = query.OrderBy(sorting);
            return query;
        }
    }
}

Now, we will change also the controller method to use the repository.

public ActionResult Index()
{
	using (var context = new MvcDemoEntities())
	{
		var repository = new Repository<User>(context);
		var list = repository.Search(u => u.Deleted == false, null, new List{ "UserRole"});
		return View(list);
	}
}

The down-side of this implementation is that the controller has to provide a context, meaning that it’s still tightly coupled with the DAL. You can say that we could move the creation of the context inside the repository. This is true but contradicts with the recommendation mentioned just a little before: “Leave transaction control to the client”. It’s true that for the moment we are only retrieving data, so we are not concerned about transactions, but soon we will need to add in our repository Add,Update and Delete methods. In order to remove this problem we will use the Unit of Work pattern.

4. The unit of work pattern Go top

As defined by Martin Fowler:

Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. […] A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you’re done, it figures out everything that needs to be done to alter the database as a result of your work.

And, as usual, some more references to MSDN for further reading:

Let’s modify our code to include also this pattern. The code for the UnitOfWork class is:

using System;

namespace HelloWorld.Code.DataAccess
{
    public class UnitOfWork : IDisposable
    {
        #region Repositories

        protected IRepository<User> UserRepository;
        public IRepository<User> GetUserRepository
        {
            get { return UserRepository ?? (UserRepository = new Repository<User>(Context)); }
        }

        #endregion

        #region UnitOfWork

        protected MvcDemoEntities Context;
        private bool _disposed;

        public UnitOfWork()
        {
            Context = new MvcDemoEntities();
            _disposed = false;
        }

        public void Save()
        {
            Context.SaveChanges();
        }

        public void Dispose()
        {
            if(!_disposed)
            {
                Context.Dispose();
                _disposed = true;
            }

            GC.SuppressFinalize(this);
        }

        #endregion
    }
}

The controller Index method will use the UnitOfWork class instead of crating a context and passing it to the repository constructor:

public ActionResult Index()
{
	var unitOfWork = new UnitOfWork();
	var repository = unitOfWork.GetUserRepository;
	var list = repository.Search(u => u.Deleted == false, null, new List { "UserRole" });
	return View(list);
}

5. Adding a pager to the list Go top

List with paging
In order to do this we have to:

  1. Add a new method to the repository for returning data in a paged manner.
  2. Add a pager in the View so the user can browse between the pages.

Because each of these topics it’s complex enough to be treated independently, I wrote two other posts to serve as a helping guide. First, let’s apply the instructions described in: Retrieve paged lists from DAL. Then we will integrate in our solution the pager described in: ASP.NET MVC – Pager HtmlHelper extention. In the end our controller Index method looks like this:

public ActionResult Index(int page = 1)
{
	var unitOfWork = new UnitOfWork();
	var repository = unitOfWork.GetUserRepository;
	var pagedList = repository.Search(u => !u.Deleted, null, new List { "UserRole" }, page, 5);

	return View(pagedList);
}

The view was changed to include the pager:

@using HelloWorld.Code.Util
@model HelloWorld.Code.DataAccess.Paging.IPagedList
@{
    ViewBag.Title = "Users list";
}
@if (!Model.CurrentPage.Any())
{
    <div>No users were found.</div>
}
else
{
    <table>
        <thead>
            <tr>
                <th>First Name</th>
                <th>Last Name</th>
                <th>E-mail</th>
                <th>Role</th>
            </tr>
        </thead>
        <tbody>
        @foreach (var user in Model.CurrentPage) {
            <tr>
                <td>@user.FirstName</td>
                <td>@user.LastName</td>
                <td>@user.Email</td>
                <td>@user.UserRole.UserRoleName</td>
            </tr>
        }
        </tbody>
    </table>
    @Html.PagedListPager(Url, new PagerHtmlRenderer(
                currentPageNumber: Model.CurrentPageNumber,
                pageSize: Model.PageSize,
                totalNumberOfItems: Model.TotalNumberOfItems,
                actionName: "Index",
                controllerName: "Users",
                routeValues: new RouteValueDictionary() { { "page", "" } },
                pageRouteValueName: "page"))
}

6. Allow sorting on the list Go top

We want to allow the user to sort the list by first name and last name. To enable this we first modify the controller action to receive two new parameters, the sort column name and the sort direction.

public ActionResult Index(int page = 1, string sort = "", string direction = "asc")
{
	var unitOfWork = new UnitOfWork();
	var repository = unitOfWork.GetUserRepository;
	var pagedList = repository.Search(u => !u.Deleted, string.IsNullOrEmpty(sort) ? null : string.Format("{0} {1}", sort, direction), new List { "UserRole" }, page, 5);

	ViewBag.Sort = sort;
	ViewBag.Direction = direction;

	return View(pagedList);
}

We pass the sorting information to the repository search method and also to the view: ViewBag.Sort, ViewBag.Direction. For the columns that allow sorting, the view will display links in the header. Each link will indicate the column name and the sort direction to apply (ascending for the first click and descending for the second). So, in the view, we will replace the table headers for ‘First Name’ and ‘Last Name’ with:

@Html.ActionLink("First Name", "Index", "Users",
	new RouteValueDictionary
		 {
				{ "page", Model.CurrentPageNumber },
				{ "sort", "FirstName" },
				{ "direction", ViewBag.Sort == "FirstName" ? (ViewBag.Direction == "asc" ? "desc" : "asc") : "asc" }
		 }, null)@Html.ActionLink("Last Name", "Index", "Users",
	new RouteValueDictionary
		{
			{ "page", Model.CurrentPageNumber },
			{ "sort", "LastName" },
			{ "direction", ViewBag.Sort == "LastName" ? (ViewBag.Direction == "asc" ? "desc" : "asc") : "asc" }
		}, null)

7. Add list filters Go top

In this step we will add a text box and a search button to allow the user to filter the list by name and/or surname. These controls will be placed in the view inside a form so we are able to post the data to the server when the user click the submit button.

@using (Html.BeginForm("Index", "Users", FormMethod.Post))
{
    @:Filter by name: <input type="text" name="filter" />
    <input type="submit" name="search" value="Search" />
}

In the controller we will get the value and apply the filter:

public ActionResult Index(int page = 1, string sort = "", string direction = "asc", string filter = "")
{
	var unitOfWork = new UnitOfWork();
	var repository = unitOfWork.GetUserRepository;
	var pagedList =
		repository.Search(
			u =>
			!u.Deleted &&
			(string.IsNullOrEmpty(filter) || u.FirstName.Contains(filter) || u.LastName.Contains(filter)),
			string.IsNullOrEmpty(sort) ? null : string.Format("{0} {1}", sort, direction),
			new List {"UserRole"}, page, 5);

	ViewBag.Sort = sort;
	ViewBag.Direction = direction;
	ViewBag.Filter = filter;

	return View(pagedList);
}

Now, we are able to filter the page but, if we filter the page and then we sort or change the page number the filter will be lost. To solve this we need to remember the filter value in our page number and sort column links so we will pass the filter value in the RouteValueDictionary for both sorting and paging.

8. Create a base view model for lists that support sorting, filtering and paging Go top

Displaying lists that allow sorting, filtering and paging is a common and repetitive task inside applications so, the next thing to do, is to create a basic view model that will allow us to reuse as much code as possible. The generic model should allow us to set:

  • the repository
  • the page size and the current page number
  • the sort expression

Then we will use this view model as the base class for our lists and we will define, inside the custom list classes, the filters to apply. We want the filters to be easy to define, so we will create an attribute that, when applied to a property will add filtering functionality to it. The main classes and their roles will be:

  • ListViewModel – provides basic paging, sorting and filtering,
  • ListViewFilterAttribute – the ListViewModel will get all the properties marked with the ListViewFilterAttribute and create ListViewFilter objects,
  • ListViewFilter – will be used to understand:
    • what’s the filter query string parameter name in the URL (in our example we will keep the filter values in the URL),
    • what’s the filter property name,
    • the value of the filter.

I will explain the code starting from how the base class will be used. Here is how the user list model will look like:

public class UsersList : ListViewModel<User>
{
	#region Filters

	[DisplayName("First name")]
	[ListViewFilter("fn")]
	public string FirstName { get; set; }

	[DisplayName("Last name")]
	[ListViewFilter("ln")]
	public string LastName { get; set; }

	public override Expression<Func<User, bool>> FilterCondition
	{
		get
		{
			return
				(u =>
				 (string.IsNullOrEmpty(FirstName) || u.FirstName.Contains(FirstName.Trim())) &&
				 (string.IsNullOrEmpty(LastName) || u.LastName.Contains(LastName.Trim())));
		}
		set { }
	}

	#endregion

	public UsersList()
	{       
		var unitOfWork = new UnitOfWork();
		Repository = unitOfWork.GetUserRepository;            
		IncludeList.Add("UserRole");
		PageSize = 5;
	}
}

In the constructor we set a default repository, the related entityto load (UserRole) and the default page size. Of course this values could be set from outside the class but the constructor it’s a convenient place for default values. Apart from this, we have two filters defined in the form of properties decorated with the attribute [ListViewFilter(“FilterUrlParameterName”)]. The filter sets the name of the query string parameter where to store the filter value between post-backs. Just to give a well known example, this is more or less the same idea as in a Google search. Below is the code for the attribute:

[System.AttributeUsage(System.AttributeTargets.Property)]
public class ListViewFilterAttribute : System.Attribute
{
	public string UlrParameterName { get; set; }

	public ListViewFilterAttribute(string ulrParameterName)
	{
		UlrParameterName = ulrParameterName;
	}
}

Using this attribute we will be able to populate (using reflection), a collection of ListViewFilter objects:

public class ListViewFilter
{
	public string PropertyName { get; set; }

	public string UrlFilterName { get; set; }

	public object Value { get; set; }
}

And the main class that actually supports paging, sorting and filtering is:

public class ListViewModel<T> : IListViewModel where T : class
{
	#region Properties

	/// <summary>
	/// The number of elements to display in the list. Default is 10.
	/// </summary>
	public int PageSize
	{
		get { return _pageSize < 1 ? 10 : _pageSize; }
		set { _pageSize = value; }
	}
	private int _pageSize;

	/// <summary>
	/// The current page to be displayed. Default is 1.
	/// </summary>
	public int CurrentPageNumber
	{
		get { return _currentPageNumber < 1 ? 1 : _currentPageNumber; }
		set { _currentPageNumber = value; }
	}
	private int _currentPageNumber;

	/// <summary>
	/// The column on which the sorting is applied.
	/// </summary>
	public string SortColumn { get; set; }

	/// <summary>
	/// The direction of the sorting. Default is ascending.
	/// </summary>
	public ListSortDirection SortDirection
	{
		get { return _sortingDirection; }
		set { _sortingDirection = value; }
	}
	private ListSortDirection _sortingDirection = ListSortDirection.Ascending;

	/// <summary>
	/// Get the current sort expression.
	/// </summary>
	public string SortExpression
	{
		get
		{
			return string.IsNullOrEmpty(SortColumn) ? string.Empty
					   : string.Format("{0} {1}", SortColumn, SortDirection == ListSortDirection.Ascending ? "asc" : "desc");
		}
	}

	/// <summary>
	/// Used to specify the related objects to include in the result.
	/// </summary>
	public List<string> IncludeList { get; private set; }

	/// <summary>
	/// A lambda expression used to filter the list
	/// </summary>
	/// <returns></returns>
	public virtual Expression<Func<T, bool>> FilterCondition { get; set; }

	/// <summary>
	/// The repository used to obtain the data
	/// </summary>
	public IRepository<T> Repository { get; set; } 

	#endregion

	#region Constructor

	public ListViewModel()
	{
		IncludeList = new List<string>();
	}

	#endregion

	#region Fill list & Paged list

	/// <summary>
	/// Returns the elements for the current page. The list is filtered and sorted.
	/// </summary>
	/// <param name="refresh">The refresh parameter is used to force the data retrieval in care the list was already filled.</param>
	/// <returns></returns>
	protected IPagedList<T> GetPagedList(bool refresh)
	{
		if (refresh || _pagedList == null)                           
			 _pagedList = Repository.Search(FilterCondition, SortExpression, IncludeList, CurrentPageNumber, PageSize);                
		
		return _pagedList;
	}

	public IPagedList<T> PagedList
	{
		get { return GetPagedList(false); }
		set { _pagedList = value; }
	}
	private IPagedList<T> _pagedList;

	/// <summary>
	/// Returns all the elements that match the filter condition. The list is sorted.
	/// </summary>
	/// <param name="refresh">The refresh parameter is used to force the data retrieval in care the list was already filled.</param>
	/// <returns></returns>
	protected IList<T> GetList(bool refresh)
	{
		if (refresh || _list == null)            
			_list = Repository.Search(FilterCondition, SortExpression, IncludeList);            

		return List;
	}

	public IList<T> List
	{
		get { return GetList(false); }
		set { _list = value; }
	}
	private IList<T> _list;

	#endregion

	#region Public methods

	/// <summary>
	/// Helper methods used for setting the current page number, the column to sort on and the sorting direction.
	/// </summary>
	/// <param name="currentPageNumber">The current page to be displayed.</param>
	/// <param name="sortColumn">The column on which the sorting is applied.</param>
	/// <param name="sortDirection">The direction of the sorting. Default is ascending.</param>
	/// <param name="requestParameters">Used to populate the filters.</param>
	public void SetParameters(int currentPageNumber, string sortColumn, string sortDirection, NameValueCollection requestParameters)
	{
		CurrentPageNumber = currentPageNumber;
		if (!string.IsNullOrEmpty(sortColumn))
		{
			SortColumn = sortColumn;
			if (string.Compare(sortDirection, "desc", StringComparison.InvariantCultureIgnoreCase) == 0 || string.Compare(sortDirection, "descending", StringComparison.InvariantCultureIgnoreCase) == 0)
				SortDirection = ListSortDirection.Descending;
		}

		//Populate the filter values
		if(requestParameters != null)
		{
			var filters = GetFilters();
			foreach (var filter in filters)
			{
				filter.Value = requestParameters[filter.UrlFilterName] ?? requestParameters[filter.PropertyName];
			}
			SetFilters(filters);
		}
	}

	/// <summary>
	/// Get a list of name and value for all the properties inside this class that are marked with the ListViewFilterAttribute.
	/// </summary>
	/// <returns></returns>
	public List<ListViewFilter> GetFilters()
	{
		return _filters ?? (_filters = (from p in GetType().GetProperties()
										let attr = p.GetCustomAttributes(typeof (ListViewFilterAttribute), true)
										where attr.Length == 1
										select new ListViewFilter
												   {
													   PropertyName = p.Name,
													   UrlFilterName =
														   ((ListViewFilterAttribute) (attr.First())).
														   UlrParameterName,
													   Value = p.GetValue(this, null)
												   }).ToList());
	}
	private List<ListViewFilter> _filters;
	
	/// <summary>
	/// Populate the model filter properties using the filter values.
	/// </summary>
	/// <param name="filters"></param>
	public void SetFilters(List<ListViewFilter> filters)
	{
		var type = GetType();
		foreach (var filter in filters)
		{
			var property = type.GetProperty(filter.PropertyName);
			if(property != null)
				property.SetValue(this, filter.Value, null);
		}
	}

	#endregion
}

Now we will use the UsersList in our controller:

public ActionResult Index(UsersList model, int page = 1, string sort = "", string direction = "")
{
	model.SetParameters(page, sort, direction, Request.Params);
	return View(model);
}

The view is a little modified too:

@using System.ComponentModel
@using HelloWorld.Code.Util
@model HelloWorld.Models.UsersList
@{
    ViewBag.Title = "Users list";
}
@using (Html.BeginForm("Index", "Users", FormMethod.Post))
{            
    @Html.LabelFor(model => model.FirstName) 
    @Html.TextBoxFor(model => model.FirstName)
    <br/> 
    @Html.LabelFor(model => model.LastName) 
    @Html.TextBoxFor(model => model.LastName)   
    <input type="submit" name="Search" value="Search"/>
}
@if (!Model.PagedList.CurrentPage.Any())
{
    <div>No users were found</div>
}
else
{
    <table>
        <thead>
            <tr>
                <th>@Html.ActionLink("First name", "Index", "Users", 
                                    Url.GetRouteValueDictionaryForList(
                                        Model, 
                                        sortColumn: "FirstName", 
                                        direction: string.Compare(Model.SortColumn, "FirstName", 
                                        StringComparison.CurrentCultureIgnoreCase) != 0 ? "asc" : Model.SortDirection == ListSortDirection.Ascending ? "desc" : "asc"), null)</th>
                <th>@Html.ActionLink("Last name", "Index", "Users", 
                                    Url.GetRouteValueDictionaryForList(
                                        Model, 
                                        sortColumn: "LastName", 
                                        direction: string.Compare(Model.SortColumn, "LastName", StringComparison.CurrentCultureIgnoreCase) != 0 ? "asc" : Model.SortDirection == ListSortDirection.Ascending ? "desc" : "asc"), null)</th>
                <th>E-mail</th>
                <th>Role</th>
            </tr>
        </thead>
        @foreach (var user in Model.PagedList.CurrentPage)
        {
            <tr>
                <td>@user.FirstName</td>
                <td>@user.LastName</td>
                <td>@user.Email</td>
                <td>@user.UserRole.UserRoleName</td>
            </tr>
        } 
    </table>
@Html.PagedListPager(Url, new PagerHtmlRenderer(
                                                currentPageNumber: Model.PagedList.CurrentPageNumber,
                                                pageSize: Model.PagedList.PageSize,
                                                totalNumberOfItems: Model.PagedList.TotalNumberOfItems,
                                                actionName: "Index",
                                                controllerName: "Users",
                                                routeValues: Url.GetRouteValueDictionaryForList(Model),
                                                pageRouteValueName: "page"))
    
}

Here you can notice an extension for the UrlHelper class Url.GetRouteValueDictionaryForList that was created to easily build a RouteValueDictionary that is used to create the URLs for paging and sorting so that the filters, the sort criteria and the paging is not lost when the page is refreshed.

public static RouteValueDictionary GetRouteValueDictionaryForList(this UrlHelper urlHelper, IListViewModel model, string sortColumn = "", string direction = "")
{
	var dictionary = new RouteValueDictionary
						 {
							 {"page", model.CurrentPageNumber},
							 {"sort", string.IsNullOrEmpty(sortColumn) ? model.SortColumn : sortColumn},
							 {"direction", string.IsNullOrEmpty(direction) ? (model.SortDirection == ListSortDirection.Ascending ? "asc" : "desc") : direction}                                    
						 };
	foreach (var filter in model.GetFilters())
	{
		dictionary.Add(filter.UrlFilterName, filter.Value);
	}

	return dictionary;
}

9. Summary Go top

In this post we created a list that supports paging, sorting and filtering. In the end we extracted the reusable parts in a generic class that we can use as the base for our lists.

You can download the complete source code from Codeplex.

ASP.NET MVC – Pager HtmlHelper extention

For my ASP.NET MVC 4 projects I created a HtmlHelper extension that renders a Google-like pager based on:

  • the current page number,
  • the page size and
  • the total number of elements in the list.

HtmlHelper - PagerThe pages will be generated as links and you can set the action to take when the user clicks on a page in two ways:

  1. define a custom JavaScript function to handle the click, this function will receive as an input parameter the requested page number, or
  2. define the URL of the link by specifying:
    • the name of the action method,
    • the name of the controller,
    • a RouteValueDictionary and
    • the key in the RouteValueDictionary where to put the requested page number.

The code for the HtmlHelper extention is:

public static class HtmlHelperExtentions
{
	/// <summary>
	/// Generate the HTML for a pager.
	/// </summary>
	/// <param name="helper"></param>
	/// <param name="urlHelper">The current Url helper associated to the View. Used for generating the URLs in the page links.</param>
	/// <param name="pagerOptions">An object that defines the pager properties.</param>
	/// <returns></returns>
	public static MvcHtmlString PagedListPager(this HtmlHelper helper, UrlHelper urlHelper, PagerHtmlRenderer pagerOptions)
	{
		return new MvcHtmlString(pagerOptions.GeneratePagerHtml(urlHelper));
	}
}

The class that defines the pager properties and renders the links is PagerHtmlRenderer.

using System.Globalization;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;

namespace HelloWorld.Code.Util.Paging
{
    /// <summary>
    /// Helper class used for rendering the pager for a list in HTML.
    /// </summary>
    public class PagerHtmlRenderer
    {
        #region Properties

        /// <summary>
        /// The name of the action method.
        /// </summary>
        public string ActionName { get; set; }

        /// <summary>
        /// The name of the controller.
        /// </summary>
        public string ControllerName { get; set; }

        /// <summary>
        /// An object that contains the parameters for a route.
        /// </summary>
        public RouteValueDictionary RouteValues { get; set; }

        /// <summary>
        /// The key in the RouteValues dictionary where to put the page number.
        /// </summary>
        public string PageRouteValueName { get; set; }

        /// <summary>
        /// The current page number. By default it is 1 (the first page).
        /// </summary>
        public int CurrentPageNumber { get; set; }

        /// <summary>
        /// The maximum number of elements in a page. If less then 1 we will consider 10 by default.
        /// </summary>
        public int PageSize
        {
            get { return _pageSize < 1 ? 10 : _pageSize; }
            set { _pageSize = value; }
        }
        private int _pageSize;

        /// <summary>
        /// The total number of elements in the list. 
        /// </summary>
        public int TotalNumberOfItems { get; set; }

        /// <summary>
        /// This will create a google-like pager that will display a limited number of page links. Deafult it is 10.
        /// </summary>
        public int MaximumNumberOfPageLinksToRender
        {
            get { return _maximumNumberOfPageLinksToRender < 1 ? 10 : _maximumNumberOfPageLinksToRender; }
            set { _maximumNumberOfPageLinksToRender = value; }
        }
        private int _maximumNumberOfPageLinksToRender;
        
        /// <summary>
        /// The HTML string to use as a container for all the pages. Example: <ul>{0}</ul> where {0} will be replaced with the page elements. Default is "{0}".
        /// </summary>
        public string ContainerHtmlFormatString
        {
            get { return string.IsNullOrEmpty(_containerHtmlFormatString) ? "{0}" : _containerHtmlFormatString; }
            set { _containerHtmlFormatString = value; }
        }
        private string _containerHtmlFormatString;
        
        /// <summary>
        /// The HTML string to use as a container for a page link. Example: <li>{0}</li> where {0} will be replaced with the page link. Default is "{0}".
        /// </summary>
        public string PageNumberHtmlFormatString
        {
            get { return string.IsNullOrEmpty(_pageNumberHtmlFormatString) ? "{0}" : _pageNumberHtmlFormatString; }
            set { _pageNumberHtmlFormatString = value; }
        }
        private string _pageNumberHtmlFormatString;
        
        /// <summary>
        /// The HTML string to use as a container for the current page number. Example: <strong><li>{0}</li></strong> where {0} will be replaced with the current page number. Default is <strong>{0}</strong>.
        /// </summary>
        public string CurrentPageNumberHtmlFormatString
        {
            get { return string.IsNullOrEmpty(_currentPageNumberHtmlFormatString) ? "<strong>{0}</strong>" : _currentPageNumberHtmlFormatString; }
            set { _currentPageNumberHtmlFormatString = value; }
        }
        private string _currentPageNumberHtmlFormatString;

        /// <summary>
        /// The HTML string used for separating two page numbers. For example you can use , as a separator. Default is ' '.
        /// </summary>
        public string SeparatorHtmlString { get; set; }

        /// <summary>
        /// True to show a go to previous page link. Default is true.
        /// </summary>
        public bool ShowPreviousLink { get; set; }

        /// <summary>
        /// The HTML/text to show inside the previous link element. Default is '&lt;'.
        /// </summary>
        public string PreviousLinkInnerHtml { get; set; }

        /// <summary>
        /// True to show a go to next page link. Default is true.
        /// </summary>
        public bool ShowNextLink { get; set; }

        /// <summary>
        /// The HTML/text to show inside the next link element. Default is '&gt;'.
        /// </summary>
        public string NextLinkInnerHtml { get; set; }

        /// <summary>
        /// True to show a go to first page link. Default is true.
        /// </summary>
        public bool ShowFirstLink { get; set; }

        /// <summary>
        /// The HTML/text to show inside the first link element. Default is '&lt;&lt;'. 
        /// </summary>
        public string FirstLinkInnerHtml { get; set; }

        /// <summary>
        /// True to show a go to last page link. Default is true.
        /// </summary>
        public bool ShowLastLink { get; set; }

        /// <summary>
        /// The HTML/text to show inside the last link element. Default is '&gt;&gt;'. 
        /// </summary>
        public string LastLinkInnerHtml { get; set; }

        /// <summary>
        /// True to hide the pager when there is only one page. Default is false.
        /// </summary>
        public bool AutoHide { get; set; }

        /// <summary>
        /// A custom JavaScript function for the page click that has as an input parameter the page number.
        /// </summary>
        public string PageLinkCustomAction { get; set; }

        #endregion

        /// <summary>
        /// Helper class used for rendering the pager for a list in HTML.
        /// </summary>
        /// <param name="currentPageNumber">The current page number. By default it is 1 (the first page).</param>
        /// <param name="pageSize">The maximum number of elements in a page. If less then 1 we will consider 10 by default.</param>
        /// <param name="totalNumberOfItems">The total number of elements in the list.</param>
        /// <param name="actionName">The name of the action method.</param>
        /// <param name="controllerName">The name of the controller.</param>
        /// <param name="routeValues">An object that contains the parameters for a route.</param>
        /// <param name="pageRouteValueName">The key in the RouteValues dictionary where to put the page number.</param>
        public PagerHtmlRenderer(int currentPageNumber, int pageSize, int totalNumberOfItems, string actionName, string controllerName, RouteValueDictionary routeValues, string pageRouteValueName)
        {
            CurrentPageNumber = currentPageNumber;
            PageSize = pageSize;
            TotalNumberOfItems = totalNumberOfItems;
            ActionName = actionName;
            ControllerName = controllerName;
            RouteValues = routeValues;
            PageRouteValueName = pageRouteValueName;

            //Defaults
            MaximumNumberOfPageLinksToRender = 10;
            CurrentPageNumberHtmlFormatString = "<strong>{0}</strong>";
            SeparatorHtmlString = " ";
            ShowPreviousLink = true;
            PreviousLinkInnerHtml = "&lt;";
            ShowNextLink = true;
            NextLinkInnerHtml = "&gt";
            ShowFirstLink = true;
            FirstLinkInnerHtml = "&lt;&lt;";
            ShowLastLink = true;
            LastLinkInnerHtml = "&gt;&gt;";
            AutoHide = false;
        }

        /// <summary>
        /// Generate the HTML for the pager based on the current object properties.
        /// </summary>
        /// <param name="urlHelper">The current Url helper associated to the View. Used for generating the URLs in the page links.</param>
        /// <returns></returns>
        public string GeneratePagerHtml(UrlHelper urlHelper)
        {
            var totalNumberOfPages = TotalNumberOfItems == 0
                                         ? 1
                                         : (TotalNumberOfItems/PageSize + (TotalNumberOfItems%PageSize > 0 ? 1 : 0));
            var currentPageNumber = CurrentPageNumber < 1
                                        ? 1
                                        : (CurrentPageNumber > totalNumberOfPages
                                               ? totalNumberOfPages
                                               : CurrentPageNumber);

            if (AutoHide && totalNumberOfPages == 1)
                return string.Empty;

            var sb = new StringBuilder();           

            //First, Previous
            if (ShowFirstLink && currentPageNumber > 1)
                AddToStringBuilder(sb, GetLinkHtml(1, FirstLinkInnerHtml, urlHelper));
           
            if (ShowPreviousLink && currentPageNumber > 1)
                AddToStringBuilder(sb, GetLinkHtml(currentPageNumber - 1, PreviousLinkInnerHtml, urlHelper));

            //Pages
            var centerPosition = MaximumNumberOfPageLinksToRender/2;
            var startPageNumber = currentPageNumber <= centerPosition ? 1 : currentPageNumber - centerPosition;
            for (var pageNumber = startPageNumber; pageNumber <= totalNumberOfPages && pageNumber < startPageNumber + MaximumNumberOfPageLinksToRender; pageNumber++)
            {
                AddToStringBuilder(sb,
                                   pageNumber == currentPageNumber
                                       ? string.Format(CurrentPageNumberHtmlFormatString, pageNumber)
                                       : string.Format(PageNumberHtmlFormatString, GetLinkHtml(pageNumber, pageNumber.ToString(CultureInfo.InvariantCulture), urlHelper)));
            }

            //Next, Last
            if (ShowNextLink && currentPageNumber < totalNumberOfPages)
                AddToStringBuilder(sb, GetLinkHtml(currentPageNumber + 1, NextLinkInnerHtml, urlHelper));
            if (ShowLastLink && currentPageNumber < totalNumberOfPages)
                AddToStringBuilder(sb, GetLinkHtml(totalNumberOfPages, LastLinkInnerHtml, urlHelper));

            return string.Format(ContainerHtmlFormatString, sb);
        }
        
        protected string GetLinkHtml(int pageNumber, string innerHtml, UrlHelper urlHelper)
        {
            return string.Format("<a href=\"{0}\">{1}</a>", string.IsNullOrEmpty(PageLinkCustomAction) ? GetPageLink(pageNumber, urlHelper) : GetCustomAction(pageNumber), innerHtml);
        }

        protected string GetCustomAction(int pageNumber)
        {
            return string.Format("javascript:{0}(1);", PageLinkCustomAction);
        }

        protected string GetPageLink(int pageNumber, UrlHelper urlHelper)
        {
            if (RouteValues != null && !string.IsNullOrEmpty(PageRouteValueName) && RouteValues.ContainsKey(PageRouteValueName))
                RouteValues[PageRouteValueName] = pageNumber;

            return string.IsNullOrEmpty(ActionName) || string.IsNullOrEmpty(ControllerName) ? "#" : urlHelper.Action(ActionName, ControllerName, RouteValues);
        }

        /// <summary>
        /// Add the string to the string builder. If the string builder is not emplty add before a separator.
        /// </summary>
        /// <param name="sb"></param>
        /// <param name="textToAdd"></param>
        protected void AddToStringBuilder(StringBuilder sb, string textToAdd)
        {
            if (sb.Length > 0)
                sb.Append(SeparatorHtmlString);
            sb.Append(textToAdd);
        }
    }
}

Below is an example of how to call the HtmlHelper pager extension from a view:

@Html.PagedListPager(Url, new PagerHtmlRenderer(
			//Constructor mandatory values
			currentPageNumber: 3,
			pageSize: 10,
			totalNumberOfItems: 125,
			actionName: "Index",
			controllerName: "Home",
			routeValues: new RouteValueDictionary(){{"page", ""}},
			pageRouteValueName: "page")
			{
				//Properties
				PreviousLinkInnerHtml = "<strong>Previous</strong>",
				NextLinkInnerHtml = "<strong>Next<strong>",
				FirstLinkInnerHtml = "<strong>First</strong>",
				LastLinkInnerHtml = "<strong>Last</strong>"
			})

Data validation

Contents:

  1. Introduction
  2. Adding validation errors directly to the ModelState
  3. Applying validation attributes
  4. Implementing model validation with IDataErrorInfo
  5. Implementing model validation with IValidatableObject
  6. Implementing client side validation
  7. Implementing remote validation
  8. Summary

1. Introduction

This is the second post in a series of articles about ASP.NET MVC 4. In this post I will use the previous demo application as the starting point and add data validation to the contact us form. You can download the source code for the previous post from Codeplex.

There are several ways to implement validation:

  1. Adding validation errors directly to the ModelState – in this case the validation happens in the controller where we check the posted data and, if the validation fails, we add the validation errors directly to the ModelState object. The ModelState is a property of the controller class that holds all the model-binding validation errors along with the raw values that were submitted to the server. Although this can seem to be the easiest method to apply validation it is the least recommended one because, according to the MVC pattern, the best place to implement data validation is at the model level since the model is responsible for defining the domain data and the business logic to apply on that data while the controller should implement only the application flow.
  2. Using DataAnnotation attributes – we can decorate the model properties using attributes that define common validation checks, for example we can mark a field as required or as having a given range of values. These attributes are defined in the System.ComponentModel.DataAnnotations namespace. When applied to a model property they will automatically check the value of the property and, in case of errors, will add them to the ModelState.
  3. Implementing the IDataErrorInfo interface in the model – this was the initial way of performing custom validation in MVC 1.0. Although it is still completely functional it is recommended that we use the IValidatableObject interface instead.
  4. Implementing the IValidatableObject interface in the model – IValidatableObject it’s a newer version of IDataErrorInfo used for server-side class-level validation and currently the recommended way of implementing complex server side validation that can target more than only one field.
  5. Client-side validation – some of the server side validation attributes provide also implicit client side validation. We will have a look on how to enable client-side validation.
  6. Remote Validation – allows us to make asynchronous calls to the server in order to validate the user input.

Next I will try to give an example for each validation method using the ‘Contact Us’ form that we created in the previous tutorial. This is what we did until now:

  1. We used Entity Framework (EF) to generate the data access classes for Messages and ContactReasons.The DB structure
  2. We created a new file under the Models folder where we added an additional partial class for the Message class that was generated by EF.
  3. In the same file we created an associated metadata class, called MessageMetaData, that we used to add attributes to some of the properties defined in the Message class. We need this additional class because, if we add attributes directly to the properties generated by EF, then, the next time the EF model will be modified/refreshed, the class would be recreated from scratch and our modifications would be lost.
  4. We linked the class MessageMetaData with our partial definition of the Message class using the MetadataTypeAttribute. In this moment our model definition looks like this:
    namespace HelloWorld.Code.DataAccess
    {
        [MetadataType(typeof(MessageMetaData))]
        public partial class Message
        {
           //Code omitted for brevity
        }
    
        public class MessageMetaData
        {
            [Display(Name = "E-mail")]
            public string Email { get; set; }
    
            [Display(Name = "Contact reason")]
            public int ContactReasonId { get; set; }
    
            [Display(Name = "Message")]
            public int Message1 { get; set; }
        }
    }
    

Now let’s add the following validation rules:
Contact Us Validation

  • the name is mandatory and it must be a string of at least 3 characters and at most 50 characters,
  • the e-mail is mandatory and must be a valid e-mail address with no more than 50 characters,
  • the user must select a contact reason,
  • the subject must be a string of at most 100 characters,
  • the message is required.

2. Adding validation errors directly to the ModelState

We will change the controller method that handles the HttpPost to include model validation and save the data only if the validation passed:

[HttpPost]
public ActionResult ContactUs(Code.DataAccess.Message message)
{
	//Name
	if(string.IsNullOrEmpty(message.Name))
		ModelState.AddModelError("Name", "Required");
	else if(message.Name.Length ❤ || message.Name.Length > 50)
		ModelState.AddModelError("Name", "The name must be a string of at least 3 characters and at most 50 characters");

	//Email
	if (string.IsNullOrEmpty(message.Email))
		ModelState.AddModelError("Email", "Required");
	else if (message.Email.Length > 50)
		ModelState.AddModelError("Email", "The e-mail can't have more than 50 characters");
	else if (!Regex.IsMatch(message.Email, @"^\w+([-+.]*[\w-]+)*@(\w+([-.]?\w+)){1,}\.\w{2,4}$"))
		ModelState.AddModelError("Email", "Invalid e-mail");

	//ContactReasonId
	if(!message.ContactReasonId.HasValue)
		ModelState.AddModelError("ContactReasonId", "Required");

	//Subject
	if (string.IsNullOrEmpty(message.Subject))
		ModelState.AddModelError("Subject", "Required");
	else if (message.Subject.Length > 100)
		ModelState.AddModelError("Subject", "The subject must be a string with a maximum length of 100 characters");

	//Message
	if (string.IsNullOrEmpty(message.Message1))
		ModelState.AddModelError("Message1", "Required");

	if (ModelState.IsValid)
	{
		string saveResultMessage;
		ViewBag.SaveResult = message.Save(out saveResultMessage);
		ViewBag.SaveResultMessage = saveResultMessage;
	}
	return View(message);
}

Now, in order to display the validation errors, we will add into the view a validation element @Html.ValidationMessageFor(model => model.[[FieldName]]) for each of the fields in our model:

@model HelloWorld.Code.DataAccess.Message
@{
    ViewBag.Title = "Contact Us";
}
<h2>Contact Us</h2>
<hr />
@using (Html.BeginForm("ContactUs", "Home", FormMethod.Post))
{
    @Html.LabelFor(model => model.Name, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Name)
    @Html.ValidationMessageFor(model => model.Name)

    @Html.LabelFor(model => model.Email, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Email)
    @Html.ValidationMessageFor(model => model.Email)

    @Html.LabelFor(model => model.ContactReasonId, new { @class = "control-label" })
    @Html.DropDownListFor(model => model.ContactReasonId, Model.ContactReasonsList)
    @Html.ValidationMessageFor(model => model.ContactReasonId)

    @Html.LabelFor(model => model.Subject, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Subject)
    @Html.ValidationMessageFor(model => model.Subject)

    @Html.LabelFor(model => model.Message1, new { @class = "control-label" })
    @Html.TextAreaFor(model => model.Message1)
    @Html.ValidationMessageFor(model => model.Message1)

    <input type="submit" name="Save" value="Save" />
}
@if (ViewBag.SaveResult != null && ViewBag.SaveResult)
{
    <span style="color: green;">@ViewBag.SaveResultMessage</span>
}
@if (ViewBag.SaveResult != null && !ViewBag.SaveResult)
{
    <span style="color: red;">@ViewBag.SaveResultMessage</span>
}

I also modified a little the CSS in “~/Shared/_Layout.cshtml” in order to color the error messages in red:

            body {
                 margin:0; 
                 padding:0;
            }
            #header_container {
                 background: black; 
                 color: white; 
                 height:60px; 
                 left:0; 
                 position:fixed; 
                 width:100%; 
                 top:0;
            }
            #header {
                 line-height:60px; 
                 margin:0 auto; 
                 padding-left: 50px; 
                 font-weight: bold;
                 font-size: 18pt;
            }
            #container {
                 margin:0 auto; 
                 overflow:auto; 
                 padding:80px 0; 
                 width:940px;
            }
            #container input[type=text], select, textarea  
            {
                width: 200px;
                margin: 2px;
                box-sizing: border-box;
                -moz-box-sizing: border-box;
                -webkit-box-sizing: border-box;
            }
            .control-label {
                width: 100px;
                display: inline-block;
                vertical-align: top;
            }
            .field-validation-error {
                color: red;
                vertical-align: top;
                padding-left: 5px;
            }
            .validation-summary-errors {
                color: red;
            }

If you run the site you will notice that the validation is applied. Anyway the code above is not nice, we repeated the required check a lot of times which contradicts with the DRY (Don’t Repeat Yourself) principle and, even worse, we placed model specific rules inside the controller. We will improve this in the next section by moving the validation inside the model and applying validation attributes.

3. Applying validation attributes

The System.ComponentModel.DataAnnotations namespaces contains a set of attributes that provide common validation capabilities. We will apply them in the MessageMetaData class in the same way we applied the [Display(“…”)] attribute for changing the UI text associated with a property.

public class MessageMetaData
{
	[Required(ErrorMessage = "Required")]
	[StringLength(50, MinimumLength = 3, ErrorMessage = "The name must be a string of at least 3 characters and at most 50 characters")]
	public string Name { get; set; }

	[Required(ErrorMessage = "Required")]
	[StringLength(50, ErrorMessage = "The e-mail can't have more than 50 characters")]
	[RegularExpression(@"^\w+([-+.]*[\w-]+)*@(\w+([-.]?\w+)){1,}\.\w{2,4}$", ErrorMessage = "Invalid e-mail")]
	[Display(Name = "E-mail")]
	public string Email { get; set; }

	[Required(ErrorMessage = "Required")]
	[Display(Name = "Contact reason")]
	public int? ContactReasonId { get; set; }

	[Required(ErrorMessage = "Required")]
	[StringLength(100, ErrorMessage = "The subject must be a string with a maximum length of 100 characters")]
	public string Subject { get; set; }

	[Required(ErrorMessage = "Required")]
	[Display(Name = "Message")]
	public string Message1 { get; set; }
}

And, of course, we will remove the previous code we added in the controller for managing validation:

[HttpPost]
public ActionResult ContactUs(Code.DataAccess.Message message)
{
	if (ModelState.IsValid)
	{
		string saveResultMessage;
		ViewBag.SaveResult = message.Save(out saveResultMessage);
		ViewBag.SaveResultMessage = saveResultMessage;
	}
	return View(message);
}

4. Implementing model validation with IDataErrorInfo

When Entity Framework generates an entity class, it automatically creates two partial methods for each property of the class:

  1. OnChanging method, that is called right before the corresponding property is changed and
  2. OnChanged method, that is called right after the property is changed.

So, for the models that are based on Entity Framework, we can add validation for each individual field by implementing the partial method that handles the changes for that data field. For example, we will add validation for the Name field making sure that the user entered at least two words (name and surname). We will provide an implementation for the partial method OnNameChanging inside the partial class definition that we created inside the Models folder.

[MetadataType(typeof(MessageMetaData))]
public partial class Message
{
	#region Validation

	partial void OnNameChanging(string value)
	{
		if(string.IsNullOrEmpty(value) || value.Split(new []{" "}, StringSplitOptions.RemoveEmptyEntries).Length < 2)
			throw new ValidationException("Please enter your name and surname");
	}

	#endregion

	//code removed for brevity
}

At this point, if we run the site, we will notice that the validation is applied but the error message is different than the one we’ve set in the ValidationException:
Using ValidationExceptionIn order to pass the validation messages from the model to the user interface we will implement in the model the IDataErrorInfo interface.

[MetadataType(typeof(MessageMetaData))]
public partial class Message : IDataErrorInfo
{
	#region Validation

	partial void OnNameChanging(string value)
	{
		if(string.IsNullOrEmpty(value) || value.Split(new []{" "}, StringSplitOptions.RemoveEmptyEntries).Length < 2)
			_errors.Add("Name", "Please enter your name and surname");
	}

	#region IDataErrorInfo Members

	public string Error { get; private set; }

	private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();
	public string this[string columnName]
	{
		get
		{
			return _errors.ContainsKey(columnName) ? _errors[columnName] : string.Empty;
		}
	}

	#endregion

	#endregion

	//Code omitted for brevity
}

Now we are able to see the correct error message because, when the posted form is bound to the model, the model binder checks to see if the model class implements the IDataErrorInfo interface. If it does, then it will invoke the IDataErrorInfo.this indexer for each property of the class in order to check for errors and add them to the ModelState dictionary. The model binder will also check the value of the property IDataErrorInfo.Error. This property can be used to set validation errors that are not specific to any property. We can use it to enforce complex validation rules that target multiple fields in the model.

Although IDataErrorInfo is completely supported in MVC 4 it is recommended that we use IValidatableObject which is a newer interface that was introduced starting with .NET framework 4.0.

5. Implementing model validation with IValidatableObject

This interface contains only one method IEnumerable Validate(ValidationContext validationContext) that is supposed to contain all the validation rules that a model needs to pass. Note that the model binder will call the Validate method only if there are no errors in the model’s properties.
We will extend our model class to include 2 new validation rules:
– The subject and the message can’t contain the same text,
– We can’t add a new message if we already have in the DB another message with the same subject and from the same user.

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
	//Thow an error if the subjct and the message are the same
	if (string.Compare(Subject.Trim(), Message1.Trim(), true) == 0)
	{
		yield return new ValidationResult("The subject and the message can't be the same.");
	}

	//Throw an error there is another message with the same subject from the same person
	using (var context = new MvcDemoEntities())
	{
		var existingMessage = context.Messages.FirstOrDefault(m => string.Compare(m.Name.Trim(), Name.Trim(), StringComparison.InvariantCultureIgnoreCase) == 0 &&
																   string.Compare(m.Subject.Trim(), Subject.Trim(), StringComparison.InvariantCultureIgnoreCase) == 0);
		if(existingMessage != null)
		{
			yield return new ValidationResult("A message with the same subject was previously saved in the DB.", new []{"Subject"});
		}
	}
}

The IValidatableObject.Validate() method can apply validation rules that target multiple properties, and can yield back multiple validation errors. Each ValidationResult object contains an optional list of property names that caused the rule violation, which will be used when displaying the error messages in the UI. In our example you can see that the validation error message “The subject and the message can’t be the same.” is not related to any specific model property while the other error message (“A message with the same subject was previously saved in the DB.”) is associated to the Subject field. The errors that are associated to properties will be displayed in the view by @Html.ValidationMessageFor(model => model.[[FieldName]]). In order to display also the general errors that are related to the model state in general and not to a specific field we can use @Html.ValidationSummary(true). The true parameter inside the helper method indicates that we want to display in the validation summary only the error messages that are unrelated to properties. Now our view looks like this:

@model HelloWorld.Code.DataAccess.Message
@{
    ViewBag.Title = "Contact Us";
}
<h2>Contact Us</h2>
<hr/>
@Html.ValidationSummary(true)
@using (Html.BeginForm("ContactUs", "Home", FormMethod.Post))
{
    @Html.LabelFor(model => model.Name, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Name)
    @Html.ValidationMessageFor(model => model.Name)
    <br/>
    @Html.LabelFor(model => model.Email, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Email)
    @Html.ValidationMessageFor(model => model.Email)
    <br/>
    @Html.LabelFor(model => model.ContactReasonId, new { @class = "control-label" })
    @Html.DropDownListFor(model => model.ContactReasonId, Model.ContactReasonsList)
    @Html.ValidationMessageFor(model => model.ContactReasonId)
    <br/>
    @Html.LabelFor(model => model.Subject, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Subject)
    @Html.ValidationMessageFor(model => model.Subject)
    <br/>
    @Html.LabelFor(model => model.Message1, new { @class = "control-label" })
    @Html.TextAreaFor(model => model.Message1)
    @Html.ValidationMessageFor(model => model.Message1)
    <br/>          
    <input type="submit" name="Save" value="Save" />  
}
@if (ViewBag.SaveResult != null && ViewBag.SaveResult)
{
    <span style="color: green;">@ViewBag.SaveResultMessage</span>
}
@if (ViewBag.SaveResult != null && !ViewBag.SaveResult)
{
    <span style="color: red;">@ViewBag.SaveResultMessage</span>
}

And if we run the the site we can see the validation we added with IValidatableObject.
IValidatableObject result

6. Implementing client side validation

The MVC framework comes with some built-in client-validation support. Basically, the DataAnnotation attributes are able to support also client-side validation and, in this section, I will show the steps you need to do in order to enable it.

  • First make sure that the client-side validation is enabled in the web.config that is, the following two properties are set to true:
    <appSettings>
         <add key="ClientValidationEnabled" value="true"/>
         <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
    </appSettings>
    

    Notes:
    – This enables client-side validation for the entire site, if you want to enable it only for specific views, you can do this using code blocks inside the view and calling HtmlHelper.ClientValidationEnabled(true) and HtmlHelper.UnobtrusiveJavaScriptEnabled(true).
    – The MVC Framework supports unobtrusive client-side validation, meaning that the validation rules are defined using HTML data attributes instead of emitting client side JavaScript. Then, these attributes are processed with JavaScript in order to add the validation rules. The current MVC framework validation is based on the jQuery Validation library.

  • Then we have to add in our project the JavaScript files needed for validation. The easiest way is to use NuGet. Right click on References in Solution Explored and click on ‘Manage NuGet Packages…’. In the window that opens make sure that the Online section is selected and search for jQuery. Fron here you can install: jQuery, jQuery Validation and Microsoft jQuery Unobstructive Validation.
    Adding validation libraries using NuGet
  • Now we will include those files in the _Layout.cshtml file, just before the “scripts” section, so they will be available in all our pages.
    <script type="text/javascript" src="~/Scripts/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="~/Scripts/jquery.validate.js"></script>
    <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.js"></script>
    @RenderSection("scripts", required: false)
    

    Note: It would be better to include those files using Bundling, which is a new feature in asp.net MVC 4 that allows us to easily combine/bundle multiple JavaScript/CSS files into a single file so that we reduce the number of file requests which will improve the first page load performance. But, for keeping things simple we don’t use bundling for the moment.

  • If you run now the application you will notice that the validation we added with DataAnnotation attributes takes place on the client-side with no post back to the server.

7. Implementing remote validation

Sometimes we need to make some checks that require some server-side resources and can not be implemented only with client-side code. For example, in the context of user accounts, when registering a new account we might want to check if a username is not already taken. We can implement this with as a server-side validation but this will generate a round-trip to the server and it would be nicer to make the check via Ajax. The MVC framework provides support for remote validation with the help of the [Remote] attribute. Let’s use it to check if there is another message in the DB from the same e-mail address. We will add a remote attribute to the Email property:

[Remote("ValidateEmail", "Home")]

The first parameter specifies the method used for validation and the second parameter indicates the name of the controller that contains the validation method. So in our Home controller we will add:

public JsonResult ValidateEmail(string email)
{
	string message;
	return Message.EmailAlradyExists(email, out message)
			   ? Json(message, JsonRequestBehavior.AllowGet)
			   : Json(true, JsonRequestBehavior.AllowGet);
}

And now we add inside the model the method that actually connects to the DB to check if the e-mail was already used. Please remember that we shouldn’t place data access calls or business rule validation inside the controller.

public static bool EmailAlradyExists(string email, out string validationMessage)
{
	validationMessage = string.Empty;
	using (var context = new MvcDemoEntities())
	{
		var result = context.Messages.Any(m => string.Compare(m.Email, email, StringComparison.CurrentCultureIgnoreCase) == 0);
		if (result)
			validationMessage = "This e-mail address is already used";

		return result;
	}
}

So, when we use the [Remote(“ActionMethodName”,”ControllerName”)] attribute the MVC framework uses jQuery to make an asynchronous call to the server in order to check the validation rule defined in the action method. This explains why we are returning a JsonResult from the ActionMethodName. Now, if you run the site and enter an already used e-mail, when the e-mail text box looses the focus, the remote validation will be fired and you will see an error message like in the image below.
Remote validation

8. Summary

In this post we implemented data validation at the model level and we displayed the error messages in the user interface making use of the helper methods Html.ValidationMessageFor and Html.ValidationSummary.

We showed that even if it is possible to add validation rules in the controller it is not recommended to do so because the controller should contain only logic related to the application flow control hence we should always implement data validation in the model because it’s its role to define the domain data and the business logic to apply on that data.

We showed how DataAnnotation attributes can provide an easy way to validate individual properties on the model and, for more complex validation validation rules, we implemented the IValidatableObject interface.

Finally you can download the source code of this second MVC demo application from Codeplex.

Introduction to MVC

Contents:

  1. Basic concepts
  2. Advantages of MVC
  3. From HTTP to Routes
  4. A Hello World application
  5. Adding a master page
  6. How to create a ‘Contact us’ form

1. Basic concepts

The Microsoft ASP.NET Model-View-Controller (MVC) is a web development framework that comes as an alternative to the standard ASP.NET Web Forms model. This alternative is based on a common software architecture design pattern called MVC.

A design pattern is a general reusable solution to a commonly occurring problem and can be seen as a best practice in the field.

The main purpose of MVC is to separate an application into 3 distinct parts so that the complexity of each part is decreased. This idea is very similar to the divite et impera concept. In addition to this separation MVC defines also the interactions between these 3 components:

  • Model – contains the data and the business logic,
  • View – defines the user interface and displays the model data,
  • Controller – connects the view and the model and controls the input handling.

mvc In simple words, the application flow can be summarized like this: when an event occurs (e.g. a user requests a page), the controller is invoked and is its responsibility to understanding what kind of event occurred, what action to do (e.g. update some data in a model) and what view to render as a response to that event. Except for the very simple cases, the view needs to receive a model as an input in order to know what content to render. If this is the case, the controller is also responsible for instantiating the correct model and passing it to the view.

This separation into components is called in the literature separation of concerns and it helps to minimize the impact a modification in one component has on the other components. For example, if we need to change the way the data is displayed, it should be enough to change the view without having to modify the business logic inside the model.

2. Advantages of MVC

The main advantage that comes from using the MVC pattern is that the application is divided into components avoiding the mixing of the code into a single big monolithic application. Apart from this we have another set of advantages that come rather from the way the new Microsoft MVC framework is written than from the pure concept of MVC. After all MVC is only an architectural pattern, a way of structuring an application. Nothing more and nothing less!

As everybody already knows, with the Web Forms, Microsoft tried to wrap the HTML controls into .net objects that are able to automatically remember between post backs the values of their properties by making use of the view state. This has 2 negative impacts:

  1. the view state is ugly and unacceptably big for medium to large pages and
  2. the HTML that the .net controls render is usually hard to control and change. For example the id of the elements is unnecessarily long. This was fixed with the introduction of the static client id mode but still the name attribute of the controls keeps the old format.

With the MVC framework the view state is removed and we have complete control on the rendered HTML.

Another advantage that comes from the way in which the MVC web development framework is implemented and not from the MVC pattern itself is that the complicated Page Life Cycle set of events was removed and we now have a simplified execution pipeline. The main purpose of the Page Life Cycle was to hide the way in which the communication between the client and the server happens and automatically bind user interface events, like the click of a button, with server side methods that would handle that event, creating the impression that the web interaction is event-driven and similar to the way Windows applications work. Though this was a nice idea in the beginning, time proved that it adds a lot of complexity:

  • understanding the Page Life Cycle is not a trivial job, and
  • it tends to make the pages monolithic, for example, in order to execute a button’s click event handler first the page initialization and loading events are executed, making all those event methods tightly coupled.

So, with no .net wrapper controls, no view state and no page life cycle the MVC framework doesn’t try to hide the way HTTP anf HTML work and offers a more elegant and simple development framework. Now, since we no longer have the abstractions provided by the Web Forms it’s better to get started by making sure all the basic concepts about how the web actually works are clear.

3. From HTTP to Routes

The Hypertext Transfer Protocol (HTTP) is an application level response-request protocol that defines the set of rules used by 2 participants (a client and a server) in order to communicate. Or, a more memorable definition taken from this nice book RESTful Web Services: “HTTP is the one thing that all ‘animals’ on the programmable web have in common.”

When a page is requested, the browser sends a request to the web server. The first 2 lines of a request look like this:Fiddler
Note: For inspecting the request text I used Fiddler which it’s a very useful tool that can help you see all the HTTP(S) traffic between your computer and the Internet.

The first word in the request is the HTTP method (in the example: GET). It is followed by the Uniform Resource Identifier (URI) of the resource to be retrieved (http://wordpress.com/) and the HTTP version to be used for processing the command (HTTP/1.1). The second line (Host: wordpress.com) identifies the name of the website and is used by the servers that host multiple websites under a single IP address to understand what is the website the page belongs to.

So, a request message from a client to a server includes (within the first line of that message) the method to be applied to the resource, the identifier of the resource, and the protocol version to use.

The HTTP methods, also known as HTTP verbs, are tokens that indicate the action to be performed on a resource and are very important for us because we will use them together with the URIs to map an incoming browser request to a particular MVC controller action.

Below is a list of possible methods, as defined by the World Wide Web Consortium (W3C):

  1. OPTIONS – represents a request for information about the communication options available on the request/response chain identified by the request URI. This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval. Responses to this method are not cacheable. Simply said, you can use OPTIONS to check if a server allows a particular command and avoid wasting network bandwidth trying to send an unsupported request.
  2. GET – means: retrieve whatever information is identified by the request URI.
  3. HEAD – is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method can be used for testing hypertext links for validity, accessibility, and recent modification. HEAD is typically used to verify that a resource didn’t change since the browser cached it.
  4. POST – requests that the origin server accepts the entity enclosed in the request as a new subordinate of the resource identified by the request URI. The actual function performed by the POST method is determined by the server and is usually dependent on the request URI. POST is designed to allow a uniform method to cover the following functions:
    • Annotation of existing resources;
    • Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles;
    • Providing a block of data, such as the result of submitting a form, to a data-handling process;
    • Extending a database through an append operation.
  5. PUT – requests that the enclosed entity be stored under the supplied request URI. If the request URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the request URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. So, simply said, PUT allows a client to add a resource to the server at the specified URI. If the user has permissions, the server creates the file specified in the URI and copies the request body to the newly created file.
  6. DELETE – requests that the origin server deletes the resource identified by the request URI.
  7. TRACE – is used for testing or diagnostics information and allows a client to see what is being received at the other end of the request chain.
  8. CONNECT – is reserved to be used with a proxy that can dynamically switch to being a tunnel (e.g. SSL tunneling).

Notes:
– The HTTP methods are case-sensitive.
– As specified by the W3C the methods GET and HEAD MUST be supported by all general-purpose servers. All other methods are OPTIONAL; however, if they are implemented, they MUST be implemented with the semantics previously explained.

Now, after so much talking is time to move on and do something practical, and that obviously is a Hello World application.

4. A Hello World application

Let’s start with the classic ‘File’ -> ‘New’ -> ‘Project’ but this time we will create an ‘ASP.NET MVC 4 Web Application’ :D.
Create a new MVC project
The next step is to select a project template. Please choose the empty project template and then make sure that the selected view engine is Razor.
Select the project templateA view engine is a pluggable module that provides a way to insert dynamic blocks into a template and is able to render that template to HTML or whatever content type it is designed to emit. There are 2 available view engines:

  1. one is the old ASPX that uses code blocks separated by <% tags %>,
  2. the other one is Razor that uses the character @ in order to indicate the beginning of a code block and doesn’t require the explicit closing of the code block.

Razor is not a new language, and it’s actually very easy and intuitive, it helps to create a template that contains static HTML and C# code blocks that can render dynamic content. So, we will use Razor for creating the Views.

Looking at the structure of our new empty project we see that we have a dedicated folder for the controllers, one for the models and one for the views plus another folder called App_Start that contains a RouteConfig.cs file. This file is used to define the allowed URLs and the association between an URL and the controller that will process that request.
Defining the routesEach route must:

  • have an unique name, otherwise we will receive an error
  • and must define the structure of the allowed URLs. In the example, the URLs are composed by 3 parts: the controller name {controller}, the name of the method inside the controller that will handle the request {action} and an identifier {id}.

The last line defines the default values to be used in case a part of the URL is missing. For example, if the root of the web site is requested (http://site-name/) the default controller that will be called is ‘Home’ and the default action is ‘Index’. The last parameter is optional so it will be empty.

Now, let’s add a controller called ‘Home’ in order to see how the routing works. In Solution Explorer right click on the Controller folder -> Add -> Controller. Name the new controller ‘HomeController’. Note that the naming convention for controllers is: Name + Controller and using another name, for example only ‘Home’ will make the controller unreachable.
Add a new controllerLet’s replace the default implementation of the index method with the following:

        public string Index()
        {
            return "Hello world!";
        }

Now, if we run the site, we will see the text ‘Hello world!’. If we want to use also the {id} part of the URL we can change the Index action like this:

        public string Index(string id)
        {
            return string.IsNullOrEmpty(id) ? "Hello world!" : string.Format("Hello world! Your id is: {0}.", id);
        }

So calling http://site-name/home/index/Hey we will see the text: ‘Hello world! Your id is: Hey.’. Note that here we have another naming convention, that is, the name of the input parameter matches the 3rd part of our route {id}, if the name of the parameter would have been different the displayed text would be ‘Hello world!’.

Next thing to do is to add a view file for returning HTML content rather than a string. A fast way of doing this is to right click on the action name and select ‘Add View’:
Add a new view
Let’s replace the default content of the view with the following one:

@{
    ViewBag.Title = "Home";
}
<h2>Hello from the view.</h2>
@if(!string.IsNullOrEmpty(ViewBag.Id))
{
    <text>Your id is: @ViewBag.Id.</text>
}

<i>@ViewBag.Message</i>

and update the Index action method like this:

        public ActionResult Index(string id)
        {
            ViewData["Message"] = "This is a string created in the controller";
            ViewBag.Id = id;
            return View();
        }

The first thing to notice here is the ViewData which is a dictionary that we can use to pass data from the controller to the view. The ViewBag is a dynamic type created as a wrapper over the ViewData that allows us to use dynamic properties instead of strings for accessing the objects in the ViewData. As you can see in the last line of the view, we can access the value in ViewData[“Message”] by calling @ViewBag.Message.

The last line in the action method tells the rendering engine to return the view associated with this action. To find the correct view, another naming convention is applied. The implicit view should be located under the Views folder in a sub-folder with the same name as the controller (‘Home’) and the name of the file should match the name of the action method (‘Index.cshtml’). Note that it is possible to render a different view, for this we have to pass the name of the view as a parameter to the View method.

5. Adding a master page

Our next goal is to define a common layout for the entire site. To do this we have to add a file called _ViewStart.cshtml (or _ViewStart.vbhtml for VB) under the Views folder of the project.

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

_ViewStart.cshtml indicates what file to use as a master page. It is recommended that we store the common layout files under the folder ‘~/Views/Shared’ and that we prefix the name of those files with an underscore, like _Layout.cshtml so, let’s stick to the recommendations and add the folder Shared and the file _Layout.cshtml with the following content:

<!DOCTYPE html>
<html>
    <head>
       <title>MVC - @ViewBag.Title</title>
        <style type="text/css">            
            body { margin:0; padding:0; }
            #header_container { background: black; color: white; height:60px; left:0; position:fixed; width:100%; top:0; }
            #header { line-height:60px; margin:0 auto; padding-left: 50px; font-weight: bold;font-size: 18pt;}
            #container { margin:0 auto; overflow:auto; padding:80px 0; width:940px; }
            .control-label{ width: 120px;display: inline-block;}
        </style>
    </head>
    <body>       
        <div id="header_container">
            <div id="header">
                <i>=(^.^)=</i>
            </div>
        </div>              
        <div id="container">
            <div id="content">
                @RenderBody()    
            </div>
        </div>
        @RenderSection("scripts", required: false)
    </body>
</html>

The code it’s pretty straight forward:
– @RenderBody() will render the content of a view that is not within any named sections,
– @RenderSection(“scripts”, required: false) – if the view contains a section called scripts it renders the content of that section.

To define the scripts section inside Index.cshtml just add at the end of the file the following:

@section scripts{
<script type="text/javascript">
    document.getElementById("header").innerHTML += ' MVC'
</script>
}

Now, that we have such a nice layout plus some text inserted via JavaScript, let’s add a ‘Contact us’ page that will allow our visitors to praise our great design. We will save all the positive comments in the DB so we can show to the managers how great we are!

6. How to create a ‘Contact us’ form

Contact Us Form
First thing first, let’s create a DB and use Entity Framework to access it. Our DB will have these 2 tables:
The DB structure I named the new DB [MvcDemo]. Below is the script for creating the tables:

USE [MvcDemo]
GO
/****** Object:  Table [dbo].[ContactReasons]    Script Date: 03/12/2013 21:51:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[ContactReasons](
	[ContactReasonId] [int] IDENTITY(1,1) NOT NULL,
	[ContactReasonText] [nvarchar](100) NOT NULL,
 CONSTRAINT [PK_ContactReasons] PRIMARY KEY CLUSTERED 
(
	[ContactReasonId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object:  Table [dbo].[Messages]    Script Date: 03/12/2013 21:51:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Messages](
	[MessageId] [int] IDENTITY(1,1) NOT NULL,
	[Name] [nvarchar](50) NOT NULL,
	[Email] [nvarchar](50) NOT NULL,
	[ContactReasonId] [int] NULL,
	[Subject] [nvarchar](100) NOT NULL,
	[Message] [text] NOT NULL,
 CONSTRAINT [PK_Messages] PRIMARY KEY CLUSTERED 
(
	[MessageId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object:  ForeignKey [FK_Messages_ContactReasons]    Script Date: 03/12/2013 21:51:40 ******/
ALTER TABLE [dbo].[Messages]  WITH CHECK ADD  CONSTRAINT [FK_Messages_ContactReasons] FOREIGN KEY([ContactReasonId])
REFERENCES [dbo].[ContactReasons] ([ContactReasonId])
GO
ALTER TABLE [dbo].[Messages] CHECK CONSTRAINT [FK_Messages_ContactReasons]
GO

USE [MvcDemo];
SET NOCOUNT ON;
SET XACT_ABORT ON;
GO
SET IDENTITY_INSERT [dbo].[ContactReasons] ON;
BEGIN TRANSACTION;
INSERT INTO [dbo].[ContactReasons]([ContactReasonId], [ContactReasonText])
SELECT 1, N'Positive remark' UNION ALL
SELECT 2, N'Negative remark'
COMMIT;
RAISERROR (N'[dbo].[ContactReasons]: Insert Batch: 1.....Done!', 10, 1) WITH NOWAIT;
GO
SET IDENTITY_INSERT [dbo].[ContactReasons] OFF;
GO

We will use Entity Framework to connect to the DB, so let’s create a new EF file ‘MvcDemoDb.edmx’ under the folder ‘~/Code/DataAccess’. This is how the solution looks now:
Project structure after adding EFNow, let’s add a new action method in the Home controller called ContactUs and a new view for this action. This time, when creating the view, tick the ‘Create a strongly-typed view’ check-box and enter as the model class: HelloWorld.Code.DataAccess.Message.
Create a contact us viewLet’s add inside the view the code below:

@model HelloWorld.Code.DataAccess.Message

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

<h2>Contact Us</h2>
<hr/>
@using (Html.BeginForm("ContactUs", "Home", FormMethod.Post))
{
    @Html.LabelFor(model => model.Name, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Name)
    <br/>
    @Html.LabelFor(model => model.Email, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Email)
    <br/>
    @Html.LabelFor(model => model.ContactReasonId, new { @class = "control-label" })
    @Html.DropDownListFor(model => model.ContactReasonId, new List<SelectListItem>())
    <br/>
    @Html.LabelFor(model => model.Subject, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Subject)
    <br/>
    @Html.LabelFor(model => model.Message1, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Message1)
    <br/>
    <input type="submit" name="Save" value="Save" />
}

In the above code, the syntax Html.BeginForm is used to create a HTML form element. Here Html is a property of the view and it’s an instance of the HtmlHelper class. The HtmlHelper class provides methods that can be used to generate HTML elements programatically. All the methods of the HtmlHelper class generate HTML and return the result as a string. There are a lot of extension methods for the HtmlHelper class and, in the next posts, I will show how we can create our own custom extensions. So @Html.LabelFor(model => model.Name, new { @class = “control-label” }) will create a label element for the property Name of the model. With new { @class = “control-label” } we create a dictionary object that contains the HTML attributes to set to the element. In our example the rendered HTML will be:

 
<label class="control-label" for="Name">Name</label>

Try to enter some data and press the save button. You will notice that the values entered are not remembered after the post back. This is the expected behavior since we are not using the view state any more. Let’s have a look at the request with Fiddler and see what’s sent to the server.
Inspect the post data with FiddlerSo we have a POST to /Home/ContactUs that results in the action method ContactUs being called. In MVC we have a set of attributes that we can use to restrict what HTTP verbs an action method will handle. We can use [HttpGet] and [HttpPost] to indicate that an action method will be called only for GET/POST requests. Knowing this, we can modify our controller class and add a new custom action that will handle only the POST method, which, in our case, means that the user pressed the save button:

[HttpGet]
public ActionResult ContactUs()
{
	return View(new Code.DataAccess.Message());
}

[HttpPost]
public ActionResult ContactUs(Code.DataAccess.Message message)
{
	return View(message);
}

The new ContactUs method receives a message object that is filled automatically with the data the user inserted. This happens because we used in the view the HtmlHelper method @Html.TextBoxFor that created form elements with the same name as the model properties and now the MVC framework can automatically map the HTML form elements with the model properties based on the name. When rendering the response we will pass this message object back to the View and now we can notice that the data the user entered remains filled also after a post back.

The next thing that we can improve is the text that appears in the labels. For example we want to display ‘Contact reason’ instead of ‘ContactReasonId’. There is a little trick for doing this and it is called the MetadataTypeAttribute. We are going to add in our Models folder a new file and we will extent here the class HelloWorld.Code.DataAccess.Message. We can do this because the Message class is partial which means that we can split the definition of the class in more files. Please make sure that the class name and the namespace name are identical to the one generated by the entity framework model, otherwise you will be creating a new class instead of adding new elements to the existing partial class.

using System.ComponentModel.DataAnnotations;

namespace HelloWorld.Code.DataAccess
{
    [MetadataType(typeof(MessageMetaData))]
    public partial class Message
    {
    }

    public class MessageMetaData
    {
        [Display(Name = "E-mail")]
        public string Email { get; set; }

        [Display(Name = "Contact reason")]
        public int ContactReasonId { get; set; }

        [Display(Name = "Message")]
        public int Message1 { get; set; }
    }
}

The MessageMetaData defines additional attributes for our Message class. This type of helper class is called a buddy class. The display attribute sets the UI text to associate to a property and needs to be declared in a buddy class because, in this way, we don’t have to change the file generated by the EF that will be overridden the next time we will change the EF edmx file. Note that the properties defined in the MessageMetaData class are exactly the same as the ones defined in the Message class that was generated by the entity framework, otherwise the mapping would fail. If we see now the contact us page we can notice that the label text is changed. Next, we will populate the contact reason drop-down. First we define the collection in the model:

public IEnumerable<SelectListItem> ContactReasonsList
{
	get
	{
		using (var context = new MvcDemoEntities())
		{
			return (new[]
					{
						new SelectListItem
							{
								Selected = (ContactReasonId == 0),
								Text = "Select...",
								Value = string.Empty
							}
					}).Union(context.ContactReasons.
									 Select(c => new SelectListItem
									 {
										 Selected = (c.ContactReasonId == ContactReasonId),
										 Text = c.ContactReasonText,
										 Value = c.ContactReasonId.ToString(CultureInfo.InvariantCulture)
									 }));
		}                
	}
}

and then we use it in the view:

@Html.DropDownListFor(model => model.ContactReasonId, Model.ContactReasonsList)

The code above speaks for itself BUT it has a big problem: each time the view is rendered a call to the DB is made to retrieve the contact reasons. So we need to implement a basic caching mechanism. I’ll just put the code here, if you need more details check this post where you can find some explanations.

public static class CacheManager
{
	/// <summary>
	/// Get the list of contact reasons
	/// </summary>
	/// <param name="getFromCache"></param>
	/// <returns></returns>
	public static List<ContactReason> GetContactReasons(bool getFromCache)
	{
		return (List<ContactReason>)GetFromCache("ContactReasons", getFromCache, delegate
		{
			using (var context = new MvcDemoEntities())
			{
				return context.ContactReasons.OrderBy(c => c.ContactReasonText).ToList();
			}
		});
	}

	#region Private methods

	/// <summary>
	/// Helper method for adding/retrieving a value from/to the cache.
	/// </summary>
	/// <param name="cacheKey">The key of the cached item</param>
	/// <param name="f">If the key is not found in the cache execute the f() function to retrieve the object</param>
	/// <returns></returns>
	private static object GetFromCache(string cacheKey, Func<object> f)
	{
		return GetFromCache(cacheKey, true, f);
	}

	/// <summary>
	/// Helper method for adding/retrieving a value from/to the cache.
	/// </summary>
	/// <param name="cacheKey">The key of the cached item</param>
	/// <param name="getFromCache">True if a cached vaersion cand be returned. If false, f() will be used to get the data</param>
	/// <param name="f">If the key is not found in the cache execute the f() function to retrieve the object</param>
	/// <returns></returns>
	private static object GetFromCache(string cacheKey, bool getFromCache, Func<object> f)
	{
		object cachedItem = null;
		if (getFromCache)
			cachedItem = HttpRuntime.Cache[cacheKey];

		if (cachedItem == null)
		{
			cachedItem = f();
			if (cachedItem != null)
				HttpRuntime.Cache.Insert(cacheKey, cachedItem, null, DateTime.Now.AddSeconds(WebConfigSettings.CachingTime), TimeSpan.Zero);
		}

		return cachedItem;
	}

	#endregion
}

Below the modification to be done in the model:

public IEnumerable<SelectListItem> ContactReasonsList
{
	get
	{
		return (new[]
					{
						new SelectListItem
							{
								Selected = (ContactReasonId == 0),
								Text = "Select...",
								Value = string.Empty
							}
					}).Union(CacheManager.GetContactReasons(HttpContext.Current.Request.HttpMethod == "POST").
								 Select(c => new SelectListItem
								 {
									 Selected = (c.ContactReasonId == ContactReasonId),
									 Text = c.ContactReasonText,
									 Value = c.ContactReasonId.ToString(CultureInfo.InvariantCulture)
								 }));              
	}
}

And now the final touch, implementing the save with our preferred business logic (save in the DB only the positive comments). Of course, the business logic goes in the model:

public bool Save(out string message)
{
	try
	{
		using (var context = new MvcDemoEntities())
		{
			if (ContactReasonId == 1) //A positive remark
			{
				context.Messages.AddObject(this);
				message = "Your message was saved successfully. Thank you for your appreciation!";
				context.SaveChanges();
				return true;
			}                   
			message = "We are sorry you were disappointed.";
			return false;                                        
		}
	}
	catch (Exception exp)
	{
		message = "An unexpected error occured.";
		return false;
	}
}

then we call the save method from the controller

[HttpPost]
public ActionResult ContactUs(Code.DataAccess.Message message)
{
	string saveResultMessage;
	ViewBag.SaveResult = message.Save(out saveResultMessage);
	ViewBag.SaveResultMessage = saveResultMessage;
	return View(message);
}

and display the result in the view.

@if (ViewBag.SaveResult != null &amp;&amp; ViewBag.SaveResult)
{
    <span style="color:green;">@ViewBag.SaveResultMessage</span>
}
@if (ViewBag.SaveResult != null &amp;&amp; !ViewBag.SaveResult)
{
    <span style="color:red;">@ViewBag.SaveResultMessage</span>
}

This is it, now we have a completely functional ‘Contact Us’ and we can add a link to it in our default page:

@Html.ActionLink("Contact Us", "ContactUs", "Home")

Download the source code from CodePlex