Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Windows Azure, PHP, ...

NAVIGATION - SEARCH

Creating a custom ViewEngine for the ASP.NET MVC framework

Have you ever seen a presentation of ScottGu about the ASP.NET MVC framework? There is one particular slide that keeps coming back, stating that every step in the ASP.NET MVC life cycle is pluggable. Let's find out if replacing one of these components is actually easy by creating a custom ViewEngine and corresponding view.

 ASP.NET MVC life cycle

Some background

After a route has been determined by the route handler, a Controller is fired up. This Controller sets ViewData, which is afterwards passed into the ViewEngine. In short, the ViewEngine processes the view and provides the view with ViewData from the Controller. Here's the base class:

[code:c#]

public abstract class ViewEngineBase {
     public abstract void RenderView(ViewContext viewContext);
}

[/code]

By default, the ASP.NET MVC framework has a ViewEngine named WebFormsViewEngine. As the name implies, this WebFormsViewEngine is used to render a view which is created using ASP.NET web forms.

The MvcContrib project contains some other ViewEngine implementations like NVelocity, Brail, NHaml, XSLT, ...

What we are going to build...

Rendered viewIn this blog post, we'll build a custom ViewEngine which will render a page like you see on the right from a view with the following syntax:

[code:c#]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Custom ViewEngine Demo</title>
</head>
<body>
    <h1>{$ViewData.Title}</h1>
    <p>{$ViewData.Message}</p>
    <p>The following fruit is part of a string array: {$ViewData.FruitStrings[1]}</p>
    <p>The following fruit is part of an object array: {$ViewData.FruitObjects[1].Name}</p>
    <p>Here's an undefined variable: {$UNDEFINED}</p>
</body>
</html>

[/code]

What do we need?

First of all, download the current ASP.NET MVC framework from CodePlex. After creating a new ASP.NET MVC web site, tweak some stuff:

  • Remove /Views/*.*
  • Remove /Content/*.* (unless you want to keep the default CSS files)
  • Add a folder /Code

In order to create a ViewEngine, we will have to do the following:

  • Create a default IControllerFactory which sets the ViewEngine we will create on each Controller
  • Edit Global.asax.cs and register the default controller factory
  • Create a ViewLocator (this one will map a controller + action to a specific file name that contains the view to be rendered)
  • Create a ViewEngine (the actual purpose of this blog post)

Let's do some coding!

1. Creating and registering the IControllerFactory implementation

Of course, ASP.NET MVC has a default factory which creates a Controller instance for each incoming request. This factory takes care of dependency injection, including Controller initialization and the assignment of a ViewEngine. Since this is a good point of entry to plug our own ViewEngine in, we'll create an inherited version of the DefaultControllerFactory:

[code:c#]

public class SimpleControllerFactory : DefaultControllerFactory
{
    protected override IController CreateController(RequestContext requestContext, string controllerName)
    {
        Controller controller = (Controller)base.CreateController(requestContext, controllerName);
        controller.ViewEngine = new SimpleViewEngine(); // <-- will be implemented later in this post
        return controller;
    }
}

[/code]

In order to make this SimpleControllerFactory the default factory, edit the Global.asax.cs file and add the following line of code in the Application_Start event:

[code:c#]

ControllerBuilder.Current.SetControllerFactory(typeof(SimpleControllerFactory));

[/code]

2. Create a ViewLocator

In order for the ViewEngine we'll build to find the correct view for each controller + action combination, we'll have to implement a ViewLocator too:

[code:c#] 

public class SimpleViewLocator : ViewLocator
{
    public SimpleViewLocator()
    {
        base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.htm",
                                                  "~/Views/{1}/{0}.html",
                                                  "~/Views/Shared/{0}.htm",
                                                  "~/Views/Shared/{0}.html"
        };
        base.MasterLocationFormats = new string[] { "" };
    }
}

[/code]

We are actually providing the possible application paths where a view can be stored.

3. Create a ViewEngine

The moment you have been waiting for! The IViewEngine interface requires the following class structure:

[code:c#]

public class SimpleViewEngine : IViewEngine
{
    // ...
    // Private member: IViewLocator _viewLocator = null;
    // Public property: IViewLocator ViewLocator
    // ...

    #region IViewEngine Members

    public void RenderView(ViewContext viewContext)
    {
        string viewLocation = ViewLocator.GetViewLocation(viewContext, viewContext.ViewName);
        if (string.IsNullOrEmpty(viewLocation))
        {
            throw new InvalidOperationException(string.Format("View {0} could not be found.", viewContext.ViewName));
        }

        string viewPath = viewContext.HttpContext.Request.MapPath(viewLocation);
        string viewTemplate = File.ReadAllText(viewPath);

        IRenderer renderer = new PrintRenderer();
        viewTemplate = renderer.Render(viewTemplate, viewContext);

        viewContext.HttpContext.Response.Write(viewTemplate);
    }

    #endregion
}

[/code]

Note that we first locate the view using the ViewLocator, map it to a real path on the server and then render contents directly to the HTTP response. The PrintRenderer class maps {$....} strings in the view to a real variable from ViewData. If you want to see the implementation, please check the download of this example.

Conclusion

Conclusions Replacing the default ViewEngine with a custom made version is actually quite easy! The most difficult part in creating your own ViewEngine implementation will probably be the parsing of your view. Fortunately, there are some examples around which may be a good source of inspiration (see MvcContrib).

If someone wants to use the code snippets I posted to create their own PHP Smarty, please let me know! Smarty is actually quite handy, and might also be useful in ASP.NET MVC.

And yes, it has been a lot of reading, but I did not forget. Download the example code from this blog post: CustomViewEngine.zip (213.17 kb)

kick it on DotNetKicks.com

ASP.NET MVC custom ActionResult (ImageResult)

The ASP.NET MVC framework introduces the concept of returning an ActionResult in Controllers since the "preview preview" release on CodePlex. The purpose of this concept is to return a generic ActionResult object for each Controller method, allowing different child classes returning different results.

An example ActionResult (built-in) is the RenderViewResult. Whenever you want to render a view, you can simply return an object of this class which will render a specific view in its ExecuteResult method. Another example is the HttpRedirectResult which will output an HTTP header (Location: /SomethingElse.aspx).

In my opinion, this is a great concept, because it allows you to develop ASP.NET MVC applications more transparently. In this blog post, I will build a custom ActionResult class which will render an image to the HTTP response stream.

ASP.NET MVC Custom ActionResultAs an example, let's create a page which displays the current time as an image.

One option for implementing this would be creating an ASP.NET HttpHandler which renders this image and can be used inline with a simple HTML tag: <img src="DisplayTime.ashx" />

Wouldn't it be nice to be able to do something more ASP.NET MVC-like? Let's consider the following: <%=Html.Image<HomeController>(c => c.DisplayTime(), 200, 50, "Current time")%>

1. Creating the necessary HtmlHelper extension methods

The above example code is not available in standard ASP.NET MVC source code. We'll need an extension method for this, which will map a specific controller action, width, height and alternate text to a standard HTML image tag:

[code:c#]

public static class ImageResultHelper
{
    public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height)
        where T : Controller
    {
        return ImageResultHelper.Image<T>(helper, action, width, height, "");
    }

    public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height, string alt)
        where T : Controller
    {
        string url = helper.BuildUrlFromExpression<T>(action);
        return string.Format("<img src=\"{0}\" width=\"{1}\" height=\"{2}\" alt=\"{3}\" />", url, width, height, alt);
    }

[/code]

2. The custom ActionResult class

Our new ImageResult class will inherit the abstract class ActionResult and implement its ExecuteResult method. This method basically performs communication over the HTTP response stream.

[code:c#]

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

[/code]

3. A "DisplayTime" action on the HomeController

We'll add a DisplayTime action on the HomeController class, which will return an instance of the newly created class ImageResult:

[code:c#]

public ActionResult DisplayTime()
{
    Bitmap bmp = new Bitmap(200, 50);
    Graphics g = Graphics.FromImage(bmp);

    g.FillRectangle(Brushes.White, 0, 0, 200, 50);
    g.DrawString(DateTime.Now.ToShortTimeString(), new Font("Arial", 32), Brushes.Red, new PointF(0, 0));

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

[/code]

And just to be complete, here's the markup of the index view on the HomeController:

[code:c#]

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %>
<%@ Import Namespace="MvcApplication1.Code" %>
<%@ Import Namespace="MvcApplication1.Controllers" %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <p>
        <%=Html.Image<HomeController>(c => c.DisplayTime(), 200, 50, "Current time")%>
    </p>
</asp:Content>

[/code]

Want the source code? Download it here! You can use it with the current ASP.NET MVC framework source code drop.

kick it on DotNetKicks.com  

New Team Foundation Server projects on CodePlex!

Busy times... Lots of work, some holidays here in Belgium, ... But there's always time to browse CodePlex! It is actually a good thing to do that from time to time. In the past few days, I spotted two great new projects on Team Foundation Server. Thumbs up for their authors!

TeamReview

"Use this Visual Studio Add-In to leverage Team System and the Visual Studio code object model for in-IDE code review feedback, demonstration, and review replay. The inspiration for this project are the many code reviews we've experienced that have been generally underwhelming experiences and less than optimal outcomes. To make code review less painful, and to greatly assist in distributed environment code reviews this project will add Code Review squarely into the VS.Net Team System tools."

TeamReview is, in my opinion, a great step forward regarding code reviews using Visual Studio and TFS. It consists of a new work item type (for code reviews) and a Visual Studio add-in. The presented workflow is quite easy: a developer creates a code review task for the code reviewer, he answers it with eventual comments. A complete example of this flow, including screenshots, can be found on CodePlex.

TeamReview Code Review Response

TFS Sticky Buddy

Want to know by one look what's going on in your project? Pick a work item query in TFS Sticky Buddy and immediately see what work items are on schedule and which ones are overdue. Since a screenshot might say more than words, here are 2. The left one has multiple iterations, the right one is a project on my demo VPC. Seems like I have a lot of work to do :-)

TFS Sticky Buddy TFS Sticky Buddy - A lot of work!

kick it on DotNetKicks.com

ASP.Net MVC Membership Starter Kit alternative authentication

Last week, I blogged about the ASP.Net MVC Membership Starter Kit and some of its features. Since then, Troy Goode and I are developing at warp-speed to provide a complete (Forms)Authentication starter kit for the MVC framework. Scott Guthrie also noticed our efforts, which forced us to do an official release earlier than planned.

Now when I say warp-speed, here's what to think of: we added Visual Studio item templates, a nice setup program, a demo application, ... We started with FormsAuthentication, but we have evolved into some alternatives...

OpenID authentication

You can add a route to the OpenID login action, and have an out-of-the box OpenID login form:

OpenID login

Simply enter your OpenID URL, click login. The MVC Membership Starter Kit will handle the rest for you!

More on this lightweight OpenID consumer from Mads Kristensen.

Windows Live ID authentication

For this, you'll need an application key. If you have one, you can add a route to the Windows Live ID login action, and have an out-of-the box Windows Live ID login form:

WLL login

Simply click the "Sign in" link. You will then be authenticated via Windows Live ID Web Authentication and returned to your ASP.Net MVC application when the authentication succeeds. The MVC Membership Starter Kit will handle all background processing for you!

WLL login

WLL login

More on Windows Live ID Web Authentication at dev.live.com.

Associate user with membership database

Both the OpenID and Windows Live ID authentication require you to do one "manual" step: implement the link between the membership database and the authentication method. You can simply override a virtual method in your own controller implementation, like so:

[code:c#]

protected override MembershipUser AssociateOpenIDToMembershipUser( string identity, string name, string email )
{
    // TODO: implement this to use OpenID authentication
    return null;
}

protected override MembershipUser AssociateWindowsLiveIDToMembershipUser(string userId)
{
    // TODO: implement this to use Windows Live ID authentication
    return null;
}

[/code]

What you'll have to do is return the ASP.Net membership user associated with the OpenID / Windows Live ID account.

The Windows Live ID authentication is currently only available from source control on CodePlex.

(by the way: I think this is the first OpenID and Windows Live ID implementation ever using the ASP.Net MVC framework)

kick it on DotNetKicks.com

ASP.Net MVC Membership Starter Kit

ASP.Net MVC Membership starter kit Yesterday, I read a cool blog post from Troy Goode about his new CodePlex project MvcMembership. I also noticed his call for help, so I decided to dedicate some of my evening hours to his project.

Almost every (ASP.NET) website is using some form of authentication, in most cases based on ASP.NET membership. With this in mind, Troy started an ASP.NET MVC version of this. The current release version provides a sample application containing some membership functionality:

  • FormsAuthenticationController featuring:
    • Register action & view
    • Login action & view
    • Logout action
  • FormsAuthenticationAdministrationController featuring:
    • User List action & view
    • User Detail action & view
    • Role List action & view
    • Create User action & view
    • Change/Reset Password actions
    • Role Management actions
  • Custom Action Filters, including:
    • a RequiresAnonymousAttribute
    • a RequireAnyRoleAttribute
    • a RequireEveryRoleAttribute
    • a RedirectToActionOnErrorAttribute
    • a RedirectToUrlOnErrorAttribute

After an evening of contributing code, there's additional functionality in the source control system:

  • FormsAuthenticationController featuring:
    • Reset password action & view
    • Retrieve password action & view
    • Change password action & view

Also, I've been doing some massive refactoring to this project. Everything that is "generic" for most applications is now stripped out in a separate assembly still allowing situation-specific overrides. For example, if you use this MvcMembership framework, you can simply inherit the BaseFormsAuthenticationController class in your own code:

[code:c#]

namespace MvcMembership.Controllers
{
    public class FormsAuthenticationController : StarterKit.Mvc.Membership.Controllers.BaseFormsAuthenticationController
    {
    // Nothing here... All is handled in the BaseFormsAuthenticationController class!
    }
}

[/code]

Need a custom Login action? No problem!

[code:c#]

namespace MvcMembership.Controllers
{
    public class FormsAuthenticationController : StarterKit.Mvc.Membership.Controllers.BaseFormsAuthenticationController
    {
        public override void Login()
        {
            // this is an override, additional ViewData can be set here
            base.Login();
        }
    }
}

[/code]

Want to respond to some actions inside BaseFormsAuthenticationController? No problem either!

[code:c#]

namespace MvcMembership.Controllers
{
    public class FormsAuthenticationController : StarterKit.Mvc.Membership.Controllers.BaseFormsAuthenticationController
    {
        public override void OnAfterResetPassword(string email, string userName, string newPassword)
        {
            // TODO: replace with sender e-mail address.
            MailMessage mailMessage = new MailMessage("sender@example.com", email);

            // TODO: replace with custom subject.
            mailMessage.Subject = "Your password";

            // TODO: replace with custom body.
            mailMessage.Body = string.Format("{0}, your password is: {1}.", userName, newPassword);

            // TODO: replace with the name of your SMTP server.
            SmtpClient smtpClient = new SmtpClient("localhost", 25);
            smtpClient.Send(mailMessage);
        }
    }
}

[/code]

Let's hope the ASP.NET MVC team picks this up, as I think it's something lots of users would like to see. For now, it's a separate download from CodePlex.

kick it on DotNetKicks.com  

ASP.NET MVC Framework out on CodePlex

This morning, I was browsing the new projects page on CodePlex and noticed something nice! The ASP.NET MVC team already rumoured around making the ASP.NET MVC framework source code available on CodePlex, but here it is: the ASP.NET MVC project on CodePlex.

The CodePlex project does not allow people to make their own contributions, but you can easily look under the hood or fix bugs for your production environment. How cool is that!

Happy coding!

Update 4:45 PM: Seems like I've stolen the thunder from Scott Guthrie :-) To be complete: the official announcement was made a little bit later than my unofficial announcement.  You can read about it here.

kick it on DotNetKicks.com

ASP.NET MVC - Testing issues Q and A

WTF? When playing around with the ASP.NET MVC framework and automated tests using Rhino Mocks, you will probably find yourself close to throwing your computer trough the nearest window. Here are some common issues and answers:

Q: How to mock Request.Form?

A: When testing a controller action which expects Request.Form to be a NameValueCollection, a NullReferenceException is thrown... This is due to the fact that Request.Form is null.

Use Scott's helper classes for Rhino Mocks and add the following extension method:

[code:c#]

public static void SetupFormParameters(this HttpRequestBase request)
{
    SetupResult.For(request.Form).Return(new NameValueCollection());
}

[/code]

Q: I can't use ASP.NET Membership in my controller, every test seems to go bad...

A: To test a controller using ASP.NET Membership, you should use a little trick. First of all, add a new property to your controller class:

[code:c#]

private MembershipProvider membershipProvider;

public MembershipProvider MembershipProviderInstance {
    get {
        if (membershipProvider == null)
        {
            membershipProvider = Membership.Provider;
        }
        return membershipProvider;
    }
    set { membershipProvider = value; }
}

[/code]

By doing this, you will enable the use of a mocked membership provider. Make sure you use this property in your controller instead of the standard Membership class (i.e. MembershipProviderInstance.ValidateUser(userName, password) instead of Membership.ValidateUser(userName, password)).

Let's say you are testing a LoginController which should set an error message in the ViewData instance when authentication fails. You do this by creating a mocked MembershipProvider which is assigned to the controller. This mock object will be instructed to always shout "false" on the ValidateUser method of the MembershipProvider. Here's how:

[code:c#]

LoginController controller = new LoginController();
var fakeViewEngine = new FakeViewEngine();
controller.ViewEngine = fakeViewEngine;

MockRepository mocks = new MockRepository();
using (mocks.Record())
{
    mocks.SetFakeControllerContext(controller);
    controller.HttpContext.Request.SetupFormParameters();

    System.Web.Security.MembershipProvider membershipProvider = mocks.DynamicMock<System.Web.Security.MembershipProvider>();
    SetupResult.For(membershipProvider.ValidateUser("", "")).IgnoreArguments().Return(false);

    controller.MembershipProviderInstance = membershipProvider;
}
using (mocks.Playback())
{
    controller.HttpContext.Request.Form.Add("Username", "");
    controller.HttpContext.Request.Form.Add("Password", "");

    controller.Authenticate();

    Assert.AreEqual("Index", fakeViewEngine.ViewContext.ViewName);
    Assert.IsNotNull(
        ((IDictionary<string, object>)fakeViewEngine.ViewContext.ViewData)["ErrorMessage"]
    );
}

[/code]

More questions? Feel free to ask! I'd be happy to answer them.

kick it on DotNetKicks.com

March 18 ASP.NET MVC links

Too busy this week to write large blog posts myself... Luckily other people do write interesting things on ASP.NET MVC!

Happy coding!

kick it on DotNetKicks.com

Heroes happen here - Microsoft TechDays 2008 in Belgium

Microsoft TechDays 2008

Dolmen polo Just to inform you: together with a numer of colleagues from Dolmen, I'll be attending the Microsoft TechDays 2008 in Ghent, Belgium on 12 and 13 March 2008. Want to spot me, Joris, Jeroen, Danny, ... and meet in person? Search for one of the guys in a Dolmen shirt!

Update 17/03/2008: Jeroen posted an overview of the inspiring sessions on his blog. 

OpenXML + Silverlight 2.0 = cool!

Mix '08 announced some nice things, among them the release of Silverlight 2.0 (beta), ASP.NET MVC framework (CTP 2). This morning, I saw one very cool thing in my RSS reader: TextGlow.

TextGlow is James Newton-King's newest exciting project which basically combines Silverlight 2.0 and OpenXML into a fancy web-based Word 2007 document viewer. Think about combining this with my own Word 2007 document preview handler...

TextGlow Silverlight OpenXML

kick it on DotNetKicks.com