Monday, September 14, 2009

Generic Handlers and ASP.NET routing

This article will discuss the use of generic handlers, such as .ashx files, in a web project using ASP.NET routing. This is article is based on experience with .NET 4.0 Beta 1 framework. It will probably not apply to .NET 3.5 projects, and although I believe the general concept will still be valid in future versions of ASP.NET, the next version may contain a fix that makes a workaround described here obsolete.

Should any of the functionality described in here be affected by subsequent betas or the final version of .NET 4.0 I will keep this post updated.

The Generic Handler

ASP.NET has long had the option for creating a file that could serve other data than HTML. This could be images generated dynamically, or files that could come from a binary field in a database, or maybe runtime compression of javascript files, etc. The uses are many.

The easy way to implement such a generic handler would be to create an .ashx file, and implement the process request. You could also write a class directly that implements the IHttpHandler interface, and then register this file with a specific extension for example.

In both cases, you need to implement the IHttpHandler interface.

public interface IHttpHandler
{
    void ProcessRequest(HttpContext context);
    bool IsReusable { get; }
}

The interesting function if ProcessRequest where the implementation writes the actual data, text or binary, to the output.

With the routing feature in .NET 4.0, it would be natural to map a route to either the .ashx file, or just a class that implements the IHttpHandler interface. The first case is not directly possible unfortunately, but the second case is very easy to implement. And compared to pre-routing possibilities, you don't need to create mappings in web.config.

I have not really spend a long time investigating the option of getting .ashx files to work with routing because I'm much more happy with just having a class implementing IHttpHandler. There therefore may be easy solutions possible for this scenario. But if there was, I wouldn't use them.

Why can't we use .ashx files

In the .NET 4.0 Beta 1 framework you have to add routes to the route table, using

Routes.Add(string routeName, RouteBase route)

The RouteBase class is an abstract class in the framework, and there is only one concrete implementation, the Route class. This class is constructed through one of the constructors:

public Route(string url, IRouteHandler routeHandler);
public Route(string url, RouteValueDictionary defaults, IRouteHandler routeHandler);
public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler);
public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler 

routeHandler);

In every case you need an object that implements IRouteHandler. Again, only one implementation of this interface is implemented, the PageRouteHandler that easily routes to an .aspx file. This handler does not accept an .ashx file however, as it is not a Page.

So to use .ashx files, you'd need the non-existing AshxRouteHandler. But since I don't miss it, I haven't spend time investigating how difficult it would be.

Implementing Routes for an IHttpHandler implementation

Since I have dismissed the usage of .ashx files, I will now go into the solution that I use and like, creating a route for a class that implements the IHttpHandler interface. To accomplish this, all we need to do is create a class that implements the IRouteHandler. This interface only has a single function, that is quite easy to implement.

// This is the actual http handler, the class that should handle the actual requests.
public class MyHandler : IHttpHandler
{
    ...
}

public class MyHandlerRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new MyHandler();
    }
}

// Map the route to extract an image with a specific ID
// This code is added where all the other routes are mapped.
RouteTable.Routes.Add(new Route("Image/{ImageID}", new MyHandlerRouteHandler()));

That is basically be it. There is a simple problem however. We cannot inspect the route values in our handler. Normally you would retrieve the route values like this

public class MyHandler
{
    public void ProcessRequest(HttpContext context)
    {
        int imageID = int.Parse((string)context.Request.RequestContext.RouteData.Values["ImageID"]);
    }
}

The Request.RequestContext property is not initialized however.

If you use reflector to reverse engineer and inspect the .NET framework you can see that the PageRouteHandler class injects this RequestContext

public virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
    ...
    page.Context.Request.RequestContext = requestContext;
    ...
}

But because the RequestContext property setter is marked as internal, we cannot do this in our own implementation of IHttpHandler, so we have to create a workaround for this (or maybe more correctly, a different workaround than the MS one). I would argue that this behaviour is not logical, and the RequestContext property should be set automatically by the framework. Hopefully this will be changed before the release. Here is a modified implementation of the above code that correctly retrieves the route data.

public class MyHandler : IHttpHandler
{
    public RequestContext RequestContext { get; set; }
    public void ProcessRequest(HttpContext context)
    {
        int imageID = int.Parse((string)RequestContext.RouteData.Values["ImageID"]);
        ...
    }
}

public class MyHandlerRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new MyHandler() {RequestContext = requestContext};
    }
}

Generic Solution Without a DI container

Here I will present a solution for a generic route handler so you don't need to create a new route handler class for each http handler class in the system. This solution has some requirements that are unnecessary if you are using a DI (Direct Injection) container to control instantiation of your classes.

public interface IHttpHandlerBase : IHttpHandler
{
    void RequestContext {get; set; }
}

public class GenericRouteHandler<T> : IRouteHandler
    where T : IHttpHandlerBase, new()
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var retVal = new T();
        retVal.RequestContext = requestContext;
        return retVal;
    }
} 

public class ImageHandler : IHttpHandlerBase
{
   ...
}

// This goes into the route initialization
RouteTable.Routes.Add(new Route("Image/{ImageID}", new GenericRouteHandler<ImageHandler>()));

Generic solution with a DI container

If you are using a DI container, then you can remove the need for creating a new interface. It also removes the need for the where new() generic constraint. I prefer using StructureMap as DI container. Here is my implementation for a generic route handler using StructureMap.

public class MyImageHandler : IHttpHandler
{
    RequestContext _requestContext;
    public MyImageHandler(RequestContext requestContext, 
        // Other dependencies that the class needs
        )
    {
        _requestHandler = requestHandler;
        // Store other dependencies
    }
    public void ProcessRequest(HttpContext context)
    {
        ...
    }
}

public class GenericRouteHandler<T> : IRouteHandler
    where T : IHttpHandler
{
    public IHttpHandler(RequestContext requestContext)
    {
        return ObjectFactory.With(requestContext).GetInstance<T>();
    }
}

// Route initialization
RouteTable.Routes.Add(new Route("Image/{ImageID}", new GenericRouteHandler<MyImageHandler>()));

The With() function tells StructureMap that during this specific call, this particular instance of the RequestContext should be used for any constructor parameter of that particular type. This avoids having to create plugin rules for RequestContext in structure map.

This class, as it is written here, is the one that I use in my own system.

Final Thoughts

I've been working with the ASP.NET 4.0 Beta 1 for some months now, and I really appreciate the routing feature build in. I find that this works more smoothly than other 3rd party url rewriting modules that I've investigated. This is likely because there is build in support in the core of the framework. Some 3rd party url rewriting modules required some workarounds in order to be able to handle postbacks correctly. With the build in routing this is never a problem.

And with very little work it is easy to extend this functionlity to not only work with .aspx pages, but any http handler in the system.

Pete

Wednesday, August 26, 2009

ASP.NET Web forms - Receiving events in a Repeater with ViewState disabled

Welcome to my blog, and my first blog post, ever. I will use this blog to post information, tips, tricks, patterns, etc. mainly for the .NET platform.

My first blog post is about overcoming the problem that controls that can perform postback stops working if you place them inside a repeater, and the repeater's viewstate is disabled. The page does post back, but the event is lost. It never arrives at its destination. A while ago, I created a workaround for this limitation. It cannot handle all situations, but it can handle those that I find to be the most common ones.

The Problem

Why is the ViewState important for the repeater?

When the browser requests a page, a new instance of the specific page class is created; and every control that this page contains is also instantiated, and placed in the page's control tree. When all initialization logic, event handlers etc have been processed, the control tree is rendered to HTML, and the instance that was created is forgotten. When the same user makes a postback, a new fresh instance is created for every control in the control tree.

The view state is basically a place where the control tree is serialized to, and deserialized from at post back, to allow recreating the state of the page after the post back without having to initialize the page for every post back, avoiding expensive database operations.

So if for example we have a page with a button and a repeater. When you click the button, the page does a post back. By deserializing its state from the view state, the repeater is capable of recreating its control tree, therefore being able to render the same HTML again, without having to rebind to the original data. But the viewstate can take up quite a lot of space in your page. And say for example that you don't have any buttons on the page, but you have a button inside the repeater, and when you click the button you modify the underlying data, thus you have to rebind the repeater anyway. In this case, you don't need to have the viewstate to render the same HTML again.

But when you click the button, the ASP.NET framework looks at the ID of the control that should receive the event, and calls a function on this control, namely IPostBackEventHandler.RaisePostbackEvent. But the target for the function is not the repeater itself, but the button that is located in the control tree inside the repeater. An because the control tree is not regenerated, there is no one to receive this event.

The Workaround

At one project I worked on, we had a page with four repeaters, each rendering a table with quite a lot of fields. This gave such a huge viewstate that it was giving performance problems. So I found this workaround to the problem.

There are two drawbacks to this solutions. You have to place the repeater inside its own user control. I personally don't really find this to be a drawback because I always wrap my repeaters inside their own user controls to better organize the code.

The second drawback is a bit more serious. You cannot just place any control that has post back events inside the repeater. In fact, you need to control the event generation yourself. But if your repeater only does post backs from Button or LinkButton controls, this should be fairly simple.

The reason why you have to wrap the repeater inside a user control is because the controls inside the repeater is not capable of receiving the event; we simply cannot change this fact. Therefore we must direct the event to a control outside the repeater. That control happens to be the user control that we are wrapping the repeater in, thus why we have to wrap it in a user control. This leads to the second consequence; that you cannot place any control inside a repeater. This is because controls that fire post back events have a habit of routing the event to itself. We therefore need to control the post back javascript code from the user control implementation itself.

That actually means that we cannot even use Button or LinkButton controls inside the repeater. But their behavior is very easy to reproduce, so they are easy to implement in your workaround. But say that you placed a DatePicker control inside your repeater. Then you would not be able to use this workaround. Let's move on to the example:

The example

This example is created for .NET 4.0 beta 1. I originally created this pattern for a .NET 2.0 project, so I know that the pattern works for earlier versions of the framework, but there might of course be differences in the implementation. As I have described, I have the repeater wrapped in a user control. When you create a new user control, there are basically two ways to implement the user control:

  1. The user control has the responsibility of getting data from the data source, and updating data based on event.
  2. The page (or a presenter in MVP) has the responsibility of getting data from the data source, and sends the data to the user control through a public method on this. Button clicks in the repeater will cause it to raise an event that the page handles, and updates the underlying data, and tells the repeater to update itself.

Option 2 has a more decoupled design, but also more code. As this post is not about decoupling application logic, but events in a repeater, my example code will use option 1; the repeater loads data in Page_Load, and updates data directly, when the event is received.

In my simple example, I load a bunch of objects from a data source and place them in a repeater-generated table. Each row in the table has a “delete”-link. Clicking the link will delete the object and rebind the repeater.

Let's first take a look at my “data source”:

namespace RepeaterEvent
{
    public class DataClass
    {
        public int ID { get; set; }
        public string A { get; set; }
        public string B { get; set; }
    }

    public static class DataClassContainer
    {
        public static List Objects;

        public static void Init()
        {
            Objects = new List();
            for (int i = 0; i < 20; i++)
            {
                Objects.Add(new DataClass()
                {
                    ID = i,
                    A = "A" + i,
                    B = "B" + i
                });
            }
        }
    }
}

The Init() function simply reinitializes the static array, and I call this function in the Page_Load function in my user control, if it is not a postback:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        DataClassContainer.Init();
        DataRepeater.DataSource = DataClassContainer.Objects;
        DataRepeater.DataBind();
    }
}

During postbacks, I then modify the Objects collection, and rebind the repeater to the modified collection.

In my test project, my user control is named RepeaterControl. Here is the class declaration:

namespace RepeaterEvent
{
    public partial class RepeaterControl : UserControl, IPostBackEventHandler
    {
        public void RaisePostBackEvent(string eventArgument)
        {
                ...
        }
    }
}

The user control implements IPostBackEventHandler so that it may receive postback events.

As I need to manually generate the event firing code, the “LinkButton” is replaced by a simple HTML hyperlink. I use functionality in the .NET framework generate the actual javascript for me. Let's have a look at the .ascx file.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="RepeaterControl.ascx.cs" Inherits="RepeaterEvent.RepeaterControl" %>
<asp:Repeater runat="server" ID="DataRepeater" EnableViewState="false">
  <HeaderTemplate>
    <table> <tbody>
  </HeaderTemplate>
  <FooterTemplate>
    </tbody> </table> 
  </FooterTemplate>
  <ItemTemplate>
    <tr>
      <td><%# GetA(Container.DataItem) %></td>
      <td><%# GetB(Container.DataItem) %></td>
      <td><a href="<%# GetDeleteScript(Container.DataItem) %>">Delete</a></td>
    </tr>
  </ItemTemplate>
</asp:Repeater>

The GetXYZ functions are functions that I have defined in my code behind file. I always do this instead of using the Eval function, as it will provide compile time checking of the properties that I access. If someone for example removed or renamed a property, they would receive a compile time error instead of a runtime error.

Here are the three functions.

    protected string GetA(object objDataClass)
    {
        var obj = (DataClass)objDataClass;
        return obj.A;
    }
    protected string GetB(object objDataClass)
    {
        var obj = (DataClass)objDataClass;
        return obj.B;
    }
    protected string GetDeleteScript(object objDataClass)
    {
        var obj = (DataClass)objDataClass;
        string eventArgs = "delete:" + obj.ID;
        return Page
            .ClientScript
            .GetPostBackClientHyperlink(
            this, eventArgs);
    }

As you can see, I use the Page.ClientScript.GetPostbackClientHyperlink function to generate a javascript hyperlink, “javascript:__doPostBack(...)” that I can use as the URL for my hyperlink element. The first parameter is the target for the event. The target for the event is the user control itself; so we pass “this” as the first parameter. The second parameter is a string that will be sent to the RaisePostBackEvent when the link is clicked. I use this format “delete:{id}” so that I can make something that resembles the CommandName/CommandArgument behaviour of normal repeater event.

Let's just have a look at the RaisePostbackEvent(...) function:

    public void RaisePostBackEvent(string eventArgument)
    {
        string[] args = eventArgument.Split(':');
        string command = args [0];
        string argument = args[1];
        if (command == "delete")
        {
            int id = int.Parse(argument);
            DataClassContainer.Objects.Remove(
                DataClassContainer.Objects.Find(x => x.ID == id));
            DataRepeater.DataSource = DataClassContainer.Objects;
            DataRepeater.DataBind();
        }
    }

It simply decodes the argument, and determines what function should be performed (delete), and which object is the target for this operation. It then removes it from the “database” and then rebinds the repeater to the updated data source.

So basically what we have here is something that reproduces the behaviour of having a LinkButton with a CommandName and a CommandArgument inside the repeater.

Conclusion

I have shown you that you can have events fired from inside a Repeater control, and handle them. But as it's not natively supported by the framework, there are some limitations. But that said, this pattern has proved valuable to me in the past, so if you can live with the limitations, you can chunk off a great part of your view state. And if you have as much view state as I have had, you can seriously improve user experience.

As this is my first blog post, I would appreciate comments on how it is written. Is it too long, too short. Is it clearly understandable. Were there points I should have focused more on. Should I have provided more example code?

I have a few ideas for new blog posts, one or two about using StructureMap in ASP.NET projects, and then I have planned for a long post on unit testing ASP.NET Web Forms. And I'm talking about real unit testing, stuff that you can run from NUnit console without being dependent on a web server. I'm currently writing on that one, but there is going to be a lot of text. So that will be at least 3 seperate blog posts, maybe more.

I hope you find this useful.

Pete.