Customizing and Extending the ASP.NET MVC Framework

Exclusive offer: get 50% off this eBook here
ASP.NET MVC 1.0 Quickly

ASP.NET MVC 1.0 Quickly — Save 50%

Design, develop, and test powerful and robust web applications with MVC framework the agile way

$20.99    $10.50
by Maarten Balliauw | March 2009 | Microsoft

One of the driving goals for the ASP.NET MVC framework has been to create a flexible framework in which every component can be extended or replaced by a custom solution, whether developed by you or obtained from a third-party vendor. In this article by Maarten Balliauw, you will learn how you can customize and extend the ASP.NET MVC framework.

(For more resources on .NET, see here.)

Creating a control

When building applications, you probably also build controls. Controls are re-usable components that contain functionality that can be re-used in different locations. In ASP.NET Webforms, a control is much like an ASP.NET web page. You can add existing web server controls and markup to a custom control and define properties and methods for it. When, for example, a button on the control is clicked, the page is posted back to the server that performs the actions required by the control.

The ASP.NET MVC framework does not support ViewState and postbacks, and therefore, cannot handle events that occur in the control. In ASP.NET MVC, controls are mainly re-usable portions of a view, called partial views, which can be used to display static HTML and generated content, based on ViewData received from a controller. In this topic, we will create a control to display employee details. We will start by creating a new ASP.NET MVC application using File | New | Project... in Visual Studio, and selecting ASP.NET MVC Application under Visual C# - Web. First of all, we will create a new Employee class inside the Models folder. The code for this Employee class is:

public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Department { get; set; }
}

On the home page of our web application, we will list all of our employees. In order to do this, modify the Index action method of the HomeController to pass a list of employees to the view in the ViewData dictionary. Here's an example that creates a list of two employees and passes it to the view:

public ActionResult Index()
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Our employees welcome you to our site!";
List<Employee> employees = new List<Employee>
{
new Employee{
FirstName = "Maarten",
LastName = "Balliauw",
Email = "maarten@maartenballiauw.be",
Department = "Development"
},
new Employee{
FirstName = "John",
LastName = "Kimble",
Email = "john@example.com",
Department = "Development"
}
};
return View(employees);
}

The corresponding view, Index.aspx in the Views | Home folder of our ASP.NET MVC application, should be modified to accept a List<Employee> as a model. To do this, edit the code behind the Index.aspx.cs file and modify its contents as follows:

using System.Collections.Generic;
using System.Web.Mvc;
using ControlExample.Models;
namespace ControlExample.Views.Home
{
public partial class Index : ViewPage<List<Employee>>
{
}
}

In the Index.aspx view, we can now use this list of employees. Because we will display details of more than one employee somewhere else in our ASP.NET MVC web application, let's make this a partial view.

Right-click the Views | Shared folder, click on Add | New Item... and select the MVC View User Control item template under Visual C# | Web | MVC. Name the partial view, DisplayEmployee.ascx.

ASP.NET MVC 1.0 Quickly

The ASP.NET MVC framework provides the flexibility to use a strong-typed version of the ViewUserControl class, just as the ViewPage class does. The key difference between ViewUserControl and ViewUserControl<T>is that with the latter, the type of view data is explicitly passed in, whereas the non-generic version will contain only a dictionary of objects. Because the DisplayEmployee.aspx partial view will be used to render items of the type Employee, we can modify the DisplayEmployee. ascx code behind the file DisplayEmployee.ascx.cs and make it strong-typed:

using ControlExample.Models;
namespace ControlExample.Views.Shared
{
public partial class DisplayEmployee :
System.Web.Mvc.ViewUserControl<Employee>
{
}
}

In the view markup of our partial view, the model can now be easily referenced. Just as with a regular ViewPage, the ViewUserControl will have a ViewData property containing a Model property of the type Employee. Add the following code to DisplayEmployee.ascx:

<%@ Control Language="C#" AutoEventWireup="true"
CodeBehind="DisplayEmployee.ascx.cs"
Inheits="ControlExample.Views.Shared.DisplayEmployee" %>
<%=Html.Encode(Model.LastName)%>, <%=Html.Encode(Model.FirstName)%>
<br/>
<em><%=Html.Encode(Model.Department)%></em>

The control can now be used on any view or control in the application. In the Views | Home | Index.aspx view, use the Model property (which is a List<Employee>) and render the control that we have just created for each employee:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.
Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs"
Inherits="ControlExample.Views.Home.Index" %>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent"
runat="server">
<h2><%= Html.Encode(ViewData["Message"]) %></h2>
<p>Here are our employees:</p>
<ul>
<% foreach (var employee inModel) { %>
<li>
<% Html.RenderPartial("DisplayEmployee", employee); %>
</li>
<% } %>
</ul>
</asp:Content>

In case the control's ViewData type is equal to the view page's ViewData type, another method of rendering can also be used. This method is similar to ASP.NET Webforms controls, and allows you to specify a control as a tag. Optionally, a ViewDataKey can be specified. The control will then fetch its data from the ViewData dictionary entry having this key.

<uc1:EmployeeDetails ID="EmployeeDetails1"
runat="server"
ViewDataKey="...." />

For example, if the ViewData contains a key emp that is filled with an Employee instance, the user control could be rendered using the following markup:

<uc1:EmployeeDetails ID="EmployeeDetails1"
runat="server"
ViewDataKey="emp" />

After running the ASP.NET MVC web application, the result will appear as shown in the following screenshot:

ASP.NET MVC 1.0 Quickly

ASP.NET MVC 1.0 Quickly Design, develop, and test powerful and robust web applications with MVC framework the agile way
Published: March 2009
eBook Price: $20.99
Book Price: $34.99
See more
Select your format and quantity:

Creating a filter attribute

An action filter is an attribute that can be applied to a controller class or an action method. Whenever the controller or action method is called, the action filter will be triggered both before and after the execution. Typically, action filters are used for solving problems that can occur in more than one class—the so called cross-cutting concerns. A typical cross-cutting concern is output caching or authentication—both can be required for more than one action method.

One cross-cutting concern of an action method might be logging. For example, one wants to log when an action was called, and whether its result was executed, in a log file as shown here:

[2008-09-02 - 03:03:13] Controller: Home; Action: Index; Action
executing...
[2008-09-02 - 03:03:13] Controller: Home; Action: Index; Action
executed.
[2008-09-02 - 03:03:13] Controller: Home; Action: Index; Result
executing...
[2008-09-02 - 03:03:15] Controller: Home; Action: Index; Result
executed.
[2008-09-02 - 03:04:42] Controller: Account; Action: Login; Action
executing...
[2008-09-02 - 03:04:42] Controller: Account; Action: Login; Action
executed.
[2008-09-02 - 03:04:42] Controller: Account; Action: Login; Result
executing...
[2008-09-02 - 03:04:43] Controller: Account; Action: Login; Result
executed.
[2008-09-02 - 03:04:44] Controller: Home; Action: About; Action
executing...
[2008-09-02 - 03:04:44] Controller: Home; Action: About; Action
executed.
[2008-09-02 - 03:04:44] Controller: Home; Action: About; Result
executing...
[2008-09-02 - 03:04:44] Controller: Home; Action: About; Result
executed.

To achieve this, a filter attribute can be created by implementing the IActionFilter and IResultFilter interfaces, and optionally overloading the FilterAttribute class. An action filter allows you to run code before and after an action method is called, but before the result of the action method is executed. A result filter is very similar to an action filter except that it is executed before and after the result is returned from the action that has been executed.

The IActionFilter interface defines two methods:

  • OnActionExecuting
    Runs before the action method is executed
  • OnActionExecuted
    Runs after the action method is executed, but before the ActionResult is executed

The IResultFilter interface defines two methods:

  • OnResultExecuting
    Runs before the action method's action result is executed
  • OnResultExecuted
    Runs after the action method's action result is executed

Let's create a class called LoggingAttribute which implements these two interfaces, and will run whenever an action is executing or an action result is executing.

First of all, let's define the class and apply the AttributeUsage attribute to it. This attribute tells the compiler that the LoggingAttribute we are creating can only be applied to classes and methods. The LoggingAttribute class implements IActionFilter and IResultFilter. We also add a property named LogName, which will hold the path to our log file.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = true, AllowMultiple = true)]
public class LoggingAttribute : FilterAttribute, IActionFilter,
IResultFilter
{
#region Properties
public string LogName { get; set; }
#endregion
}

Because we will be logging some things, let's also add a method that writes a log message to the file referenced by the LogName property. This method will simply open the file, append a new line to it, and close it again.

private void LogMessage(string controller, string action, string
message)
{
if (!string.IsNullOrEmpty(LogName))
{
TextWriter writer = new StreamWriter(LogName, true);
writer.WriteLine("[{0}] Controller: {1}; Action: {2}; {3}",
DateTime.Now.ToString("yyyy-MM-dd - hh:mm:ss"),
controller, action, message);
writer.Close();
}
}

Now, it is time to implement the IActionFilter and IResultFilter interfaces. For the IActionFilter, we'll add the OnActionExecuting and OnActionExecuted methods. For IResultFilter, we'll add the OnResultExecuting and OnResultExecuted methods. All of these methods will use the LogMessage method that we've just created and pass in some information for logging to the file.

public void OnActionExecuting(ActionExecutingContext filterContext)
{
LogMessage(
filterContext.RouteData.Values["controller"].ToString(),
filterContext.RouteData.Values["action"].ToString(),
"Action executing..."
);
}

public void OnActionExecuted(ActionExecutedContext filterContext)
{
LogMessage(
filterContext.RouteData.Values["controller"].ToString(),
filterContext.RouteData.Values["action"].ToString(),
"Action executed."
);
}

public void OnResultExecuting(ResultExecutingContext filterContext)
{
LogMessage(
filterContext.RouteData.Values["controller"].ToString(),
filterContext.RouteData.Values["action"].ToString(),
"Result executing..."
);
}

public void OnResultExecuted(ResultExecutedContext filterContext)
{
LogMessage(
filterContext.RouteData.Values["controller"].ToString(),
filterContext.RouteData.Values["action"].ToString(),
"Result executed.".
);
}

The source of information that is being logged originates in the parameter passed to the On... methods. This is always an object containing information about the current execution context, such as the controller that is executing, the view that is being rendered, and HTTP request data.

Here's the full LoggingAttribute class that we have been creating:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = true, AllowMultiple = true)]
public class LoggingAttribute : FilterAttribute, IActionFilter,
IResultFilter
{
#region Properties
public string LogName { get; set; }
#endregion
#region Helper methods
private void LogMessage(string controller, string action, string
message)
{
if (!string.IsNullOrEmpty(LogName))
{
TextWriter writer = new StreamWriter(LogName, true);
writer.WriteLine("[{0}] Controller: {1}; Action: {2}; {3}",
DateTime.Now.ToString("yyyy-MM-dd - hh:mm:ss"),
controller, action, message);
writer.Close();
}
}

#endregion
#region IActionFilter Members

public void OnActionExecuting(ActionExecutingContext
filterContext)
{
LogMessage(
filterContext.RouteData.Values["controller"].ToString(),
filterContext.RouteData.Values["action"].ToString(),
"Action executing..."
);
}

public void OnActionExecuted(ActionExecutedContext filterContext)
{
LogMessage(
Customizing & Extending the ASP.NET MVC Framework
[ 100 ]
filterContext.RouteData.Values["controller"].ToString(),
filterContext.RouteData.Values["action"].ToString(),
"Action executed."
);
}

#endregion
#region IResultFilter Members

public void OnResultExecuting(ResultExecutingContext
filterContext)
{
LogMessage(
filterContext.RouteData.Values["controller"].ToString(),
filterContext.RouteData.Values["action"].ToString(),
"Result executing..."
);
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
LogMessage(
filterContext.RouteData.Values["controller"].ToString(),
filterContext.RouteData.Values["action"].ToString(),
"Result executed."
);
}

#endregion
}

The filter can now be applied to any controller, which in turn will apply the filter to all of the action methods, or to individual action methods:

[Logging(LogName = "C:tempApplicationLog.log")]
public class HomeController : Controller
{

// ...
[Logging(LogName = "C:tempActionLog.log")]

public ActionResult SomeAction() {

// ...
}
}

Now, when the SomeAction action method of the HomeController is called, a log entry will be created when the view is being rendered.

Creating a custom ActionResult

The ASP.NET MVC framework's action method implements the concept of returning an ActionResult instance, which will typically render a specific view, or redirect the user to a different location on the web site. An ActionResult that renders a view is returned as a RenderViewResult . The ExecuteResult() method is called in order to render specific contents to the HTTP response stream.

An ActionResult can take any form as long as it has something to do with the HTTP response stream. For example, you can create a FileDownloadResult that streams a file on the HTTP response stream, or a PermanentRedirectResult that renders HTTP status code 302.

One of the problems that many web designers face is the fact that a user's web browser may or may not have the specific fonts needed to display the contents. This may be a problem, say, if the web designer wants to style a title element in some exotic font face. Because the ASP.NET MVC framework has a modular architecture, a custom ActionResult class can easily be created to achieve this goal. This custom ActionResult will render a JPEG image based on input text, which can be used to display a page title. This ActionResult class will be named ImageResult.

ASP.NET MVC 1.0 Quickly

The new ImageResult class will inherit the abstract class ActionResult and implement its ExecuteResult method. This method basically performs communication over the HTTP response stream. It accepts an Image object as a property as well as an ImageFormat. This means that a custom image can easily be rendered to the HTTP response stream, whether as JPEG, BMP, PNG, or GIF.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Web.Mvc;

namespace CustomActionResultExample.Code
{
public class ImageResult : ActionResult
{
public ImageResult() { }

public Image Image { get; set; }
public ImageFormat ImageFormat { get; set; }

public override void ExecuteResult(ControllerContext context)
{
// verify properties
if (Image == null)
{
throw new ArgumentNullException("Image");
}
if (ImageFormat == null)
{
throw new ArgumentNullException("ImageFormat");
}

// output
context.HttpContext.Response.Clear();

if (ImageFormat.Equals(ImageFormat.Bmp)) context.HttpContext.
Response.ContentType = "image/bmp";
if (ImageFormat.Equals(ImageFormat.Gif)) context.HttpContext.
Response.ContentType = "image/gif";
if (ImageFormat.Equals(ImageFormat.Icon)) context.HttpContext.
Response.ContentType = "image/vnd.microsoft.icon";
if (ImageFormat.Equals(ImageFormat.Jpeg)) context.HttpContext.
Response.ContentType = "image/jpeg";
if (ImageFormat.Equals(ImageFormat.Png)) context.HttpContext.
Response.ContentType = "image/png";
if (ImageFormat.Equals(ImageFormat.Tiff)) context.HttpContext.
Response.ContentType = "image/tiff";
if (ImageFormat.Equals(ImageFormat.Wmf)) context.HttpContext.
Response.ContentType = "image/wmf";

Image.Save(context.HttpContext.Response.OutputStream,
ImageFormat);
}
}
}

The ImageResult class defines two properties: Image and ImageFormat. These properties can be set in the ImageResult constructor . When the ImageResult is executed in the ExecuteResult method , the in-memory image is rendered to the HTTP response stream in the specified image format.

using System.Drawing;
using System.Drawing.Imaging;
using System.Web.Mvc;
using CustomActionResultExample.Code;

namespace CustomActionResultExample.Controllers
{
public class PageTitleController : Controller
{
public ActionResult ShowTitle(string pageTitle, int width,
int height)
{
// Create bitmap
Bitmap bmp = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bmp);

g.FillRectangle(Brushes.White, 0, 0, width, height);

// Render light gray background
g.DrawString(pageTitle, new Font("Lucida Handwriting",
height / 2),
Brushes.LightGray, new PointF(2, 2));

// Render black text on top
g.DrawString(pageTitle, new Font("Lucida Handwriting",
height / 2),
Brushes.Black, new PointF(0, 0));

// Return ImageResult
return new ImageResult { Image = bmp, ImageFormat =
ImageFormat.Jpeg };
}
}
}

The ShowTitle action method creates a new in-memory bitmap and renders a text and text shadow on a white background. This bitmap is then passed into a new ImageResult instance, which will render the image to the HTTP response stream.

As an extra feature, this image-rendered page title can be added to the web page in an easy manner.

<%=Html.PageTitle("Welcome to ASP.NET MVC!", 400, 40)%>

This HtmlHelper extension method will render an HTML image tag such as <img src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="/PageTitle/ShowTitle?pageTitle=Welcome%20to%20ASP.NET%20MVC!&width=400&height=40" width="400" height="40" alt="Welcome to ASP.NET MVC!" />. It is implemented as follows:

using System.Web.Mvc;
using CustomActionResultExample.Controllers;
using Microsoft.Web.Mvc;

namespace CustomActionResultExample.Code
{
public static class PageTitleHelper
{
public static string PageTitle(this HtmlHelper helper, string
pageTitle, int width, int height)
{
string url = LinkBuilder.BuildUrlFromExpression
<PageTitleController>(helper.ViewContext.
RequestContext, helper.RouteCollection,
c => c.ShowTitle(pageTitle, width, height));

return string.Format("<img src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="{0}" width="{1}"
height="{2}" alt="{3}" />", url, width,
height, pageTitle);
}
}
}

We have used a new namespace here (Microsoft.Web.Mvc) to make use of the LinkBuilder class. This namespace contains several classes that may be included in the ASP.NET MVC framework in future, but are currently not considered to be stable by the ASP.NET MVC development team. The MVC features can be downloaded from the official CodePlex site at http://www.codeplex.com/aspnet

If all of the classes are in place, we can now add an image-based title in our view in an easy, intuitive manner. The following code will replace the default home page by an enhanced version using our newly-created ImageResult. The HtmlHelper class now has a new method, PageTitle, which we can use to create a title image of a specified width and height.

<asp:Content ID="indexContent" 
ContentPlaceHolderID="MainContent"
runat="server">
<%=Html.PageTitle(ViewData["Message"].ToString(), 400, 40)%>
<p>
To learn more about ASP.NET MVC visit <a
href="http://asp.net/mvc" title="ASP.NET MVC
Website">http://asp.net/mvc</a>.
</p>
</asp:Content>

After running the application, the index page will look like the screenshot presented earlier in this article.

Summary

In this article, we learned how to extend the ASP.NET MVC framework. We created a control, which is also called a partial view. We learned more about filter attributes, and also created one of our own. Finally, we looked at how to create a custom ActionResult, which displays an image containing text based on a controller's action method.


Further resources on this subject:


ASP.NET MVC 1.0 Quickly Design, develop, and test powerful and robust web applications with MVC framework the agile way
Published: March 2009
eBook Price: $20.99
Book Price: $34.99
See more
Select your format and quantity:

About the Author :


Maarten Balliauw

Maarten Balliauw has a Bachelor's Degree in Software Engineering and has
about eight years of experience in software development. He started his career
during his studies where he founded a company that did web development in PHP
and ASP.NET. After graduation, he sold his shares and joined one of the largest
ICT companies in Belgium, RealDolmen, where he continued web application
development in ASP.NET and application life cycle management in Visual Studio
Team System. He is a Microsoft Certifi ed Technology Specialist in ASP.NET and
works with the latest Microsoft technologies such as LINQ and ASP.NET 3.5. He has
published many articles in both .NET and PHP literature, such as MSDN magazine
Belgium, and PHP Architect. Ever since the fi rst announcement of the ASP.NET MVC
framework, he has been following all its developments and features.

Books From Packt

 

Entity Framework Tutorial
Entity Framework Tutorial

ASP.NET 3.5 Social Networking
ASP.NET 3.5 Social Networking

VSTO 3.0 for Office 2007 Programming
VSTO 3.0 for Office 2007 Programming

ASP.NET 3.5 Application Architecture and Design
ASP.NET 3.5 Application Architecture and Design

Implementing Microsoft Dynamics NAV 2009
Implementing Microsoft Dynamics NAV 2009

Learning SQL Server 2008 Reporting Services
Learning SQL Server 2008 Reporting Services

WCF Multi-tier Services Development with LINQ
WCF Multi-tier Services Development with LINQ

C# 2008 and 2005 Threaded Programming: Beginner's Guide
C# 2008 and 2005 Threaded Programming: Beginner's Guide

 

 

No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
b
V
2
x
b
2
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software