Dynamic linq predicates

This post is actually a repost of this great article A universal PredicateBuilder that I found very useful so I decided to share it here.

Sometimes I need to build dynamically the where part of a linq query and, unfortunately, there is no straight forward, out of the box solution inside the .NET framework for doing this.

For example, imagine that you have a list of houses that are for rent, and the user can filter this list depending on: country, region, area, price range, facilities and so on. So your linq query might look something like this:

using (var context = new DatabaseEntities())
{
	var list = context.Rentals.Where(item => 
				(string.IsNullOrEmpty(countryFilterValue) || item.Country == countryFilterValue) &&
				(string.IsNullOrEmpty(regionFilterValue) || item.Region == regionFilterValue) &&
				(string.IsNullOrEmpty(areaFilterValue) || item.Area == areaFilterValue)).ToList();
}

If you’re using linq to entities to query your DB, then the SQL that will be generated will most likely be inefficient. In our example, the generated SQL for the first condition will be more or less like this:

exec sp_executesql N'SELECT 
*
FROM [dbo].[Rentals] AS [Extent1]
WHERE  (@p__linq__0 IS NULL) OR (( CAST(LEN(@p__linq__0) AS int)) = 0) OR ([Extent1].[Country] = @p__linq__1)',N'@p__linq__0 nvarchar(4000),@p__linq__1 varchar(8000)',@p__linq__0=N'',@p__linq__1=''

In the where part you will have a lot of similar conditions (one for each filer). There could be a lot of filters but almost always the user will not complete all of them. So you will end up with an inefficient query that checks if an empty parameter is null or empty.

The solution is to build dynamically the where part of the linq query. Fortunately this is possible using the code that you can find in this post: A universal PredicateBuilder

Below I did a copy/paste of the original code so I can have it forever and ever with me if I need it ūüôā

/// <summary>
/// Enables the efficient, dynamic composition of query predicates.
/// </summary>
public static class PredicateBuilder
{
    /// <summary>
    /// Creates a predicate that evaluates to true.
    /// </summary>
    public static Expression<Func<T, bool>> True<T>() { return param => true; }
 
    /// <summary>
    /// Creates a predicate that evaluates to false.
    /// </summary>
    public static Expression<Func<T, bool>> False<T>() { return param => false; }
 
    /// <summary>
    /// Creates a predicate expression from the specified lambda expression.
    /// </summary>
    public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
 
    /// <summary>
    /// Combines the first predicate with the second using the logical "and".
    /// </summary>
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.AndAlso);
    }
 
    /// <summary>
    /// Combines the first predicate with the second using the logical "or".
    /// </summary>
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.OrElse);
    }
 
    /// <summary>
    /// Negates the predicate.
    /// </summary>
    public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
    {
        var negated = Expression.Not(expression.Body);
        return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
    }
 
    /// <summary>
    /// Combines the first expression with the second using the specified merge function.
    /// </summary>
    static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
    {
        // zip parameters (map from parameters of second to parameters of first)
        var map = first.Parameters
            .Select((f, i) => new { f, s = second.Parameters[i] })
            .ToDictionary(p => p.s, p => p.f);
 
        // replace parameters in the second lambda expression with the parameters in the first
        var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
 
        // create a merged lambda expression with parameters from the first expression
        return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
    }
 
    class ParameterRebinder : ExpressionVisitor
    {
        readonly Dictionary<ParameterExpression, ParameterExpression> map;
 
        ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
        {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }
 
        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
        {
            return new ParameterRebinder(map).Visit(exp);
        }
 
        protected override Expression VisitParameter(ParameterExpression p)
        {
            ParameterExpression replacement;
 
            if (map.TryGetValue(p, out replacement))
            {
                p = replacement;
            }
 
            return base.VisitParameter(p);
        }
    }
}

Now the where part can be created dynamically, including only the filters for which the user provided a value:

using (var context = new DatabaseEntities ())
{
	var predicate = PredicateBuilder.Create<Rentals>(item => true);

	if (!string.IsNullOrWhiteSpace(countryFilterValue))
		predicate = predicate.And(item => item.Country == countryFilterValue);
	if (!string.IsNullOrWhiteSpace(regionFilterValue))
		predicate = predicate.And(item => item.Region == regionFilterValue);
	if (!string.IsNullOrWhiteSpace(areaFilterValue))
		predicate = predicate.And(item => item.Area == areaFilterValue);

	var list = context.Rentals.Where(predicate).ToList();
}
Advertisements

Orchard eager loading when using IRepository

While developing a custom module for Orchard CMS version 1.7 I created a simple 1 to many relationship as described in this article Creating 1 n and n n relations.

So I had a parent record with a property pointing to the child record:

public class ChildRecord
{
	public virtual int Id { get; set; }
	public virtual string Name { get; set; }
}
public class ParentRecord
{
	public virtual int Id { get; set; }
	public virtual ChildRecord Child{ get; set; }
	public virtual bool SelectCondition { get; set; }
}

Then, inside a controller I used the repository to get all the parent entities that satisfy a condition. Something like this:

var items = parentRepository.Table.
              Where(item => item.SelectCondition == true).ToList();

This worked fine but I wanted to eagerly load the child entity together with the parent.

The first thing I tried was to add the Aggregate attribute to the Child property inside the ChildRecord class:

public class ParentRecord
{
	public virtual int Id { get; set; }
	[Aggregate]
	public virtual ChildRecord Child{ get; set; }
	public virtual bool SelectCondition { get; set; }
}

Although the description of this attribute says:

This attribute is used to mark relationships which need to be eagerly fetched with the parent object, thus defining an aggregate in terms of DDD.

this didn’t work for me.

The solution is quite simple, but I’m posting it here just because I had some trouble finding it:

Use the Fetch extension method defined inside the class EagerFetchingExtensionMethods under the NHibernate.Linq namespace.

var items = parentRepository.Table.
              Where(item => item.SelectCondition == true).
              Fetch(c => c.ChildRecord).ToList();

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.

EncodingConverter Console Application

EncodingConverter
Here you can download a Windows console application that can be used to change the encoding of a text file. For example you can convert the content of a file that has an UTF encoding to an ANSI encoding.

How to use it:

  1. Download the application
  2. Open command prompt
  3. Launch the application passing the following arguments separated with a space:
    1. The path to the folder that contains the files to be converted. Example: C:\Files
    2. A file pattern (see the next link for details about the available wildcard characters http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/find_c_search_where.mspx?mfr=true). Example: *.csv
    3. The desired encoding. Example: ANSI

For example, let’s suppose that you’re calling the application passing the arguments: C:\Files *.csv ANSI (like in the image above). In this case, the application will convert to an ANSI encoding all the files with the extension .csv that are located under the folder C:\Files.

The complete list of available encodings can be found on MSDN.
You can see the same list by launching the console application with the command prompt and passing the argument: HELP
EncodingConverter HelpBelow is a list with the most common encodings that you can use:

Code Page Name Display Name
1200 utf-16 Unicode
1201 unicodeFFFE Unicode (Big endian)
1250 windows-1250 Central European (Windows)
1251 windows-1251 Cyrillic (Windows)
1252 Windows-1252 Western European (Windows)
1253 windows-1253 Greek (Windows)
1254 windows-1254 Turkish (Windows)
1255 windows-1255 Hebrew (Windows)
1256 windows-1256 Arabic (Windows)
1257 windows-1257 Baltic (Windows)
1258 windows-1258 Vietnamese (Windows)
12000 utf-32 Unicode (UTF-32)
12001 utf-32BE Unicode (UTF-32 Big endian)
20127 us-ascii US-ASCII
65000 utf-7 Unicode (UTF-7)
65001 utf-8 Unicode (UTF-8)

Finally, if you want to see the source code, you can find it at http://encodingconverterconsoleapplication.codeplex.com/

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(); 
}

Orchard – 404 errors

Orchard - 404 error

While developing a new custom module for Orchard and running the site locally I noticed that it wasn’t working any more:

  • all the pages returned 404 error messages
  • the home page displayed the directory listing

I assumed it was something wrong with my module so I started to search for the cause. I found some suggestions like:

  • make sure you have a content page that has the homepage property set to true
  • delete the cache file: Orchard.Web\App_Data\cache.dat
  • check for duplicated route names
  • check the log file under Orchard.Web\App_Data\Logs

And finally, the log file saved me, showing that there was a DB connection problem.

Note to myself: Always check the log file first if you have no idea what’s going wrong.

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.