About the author

J Sawyer is a developer based in Houston, TX and loves to write code, especially ASP.NET and other web-related stuff. He is currently working on implementing Team Foundation Server at a large energy company in Houston and is loving that too.

He also loves to ride his Yamaha FZ1. And sometimes his Ninja 650.

But he doesn't code and ride at the same time. That would be bad.

Url Routing in ASP.NET

January 30, 2009 9:16 PM

One of the new features in ASP.NET 3.5 SP1 is Url Routing … it’s the magic behind the pretty, clean url’s that you see with Dynamic Data and MVC. If you dig around a bit, almost all (if not all) of the material that’s out there focuses on using Routing with either MVC or Dynamic Data … I’ve found nada that actually talks about how it can be added to an existing ASP.NET WebForms application. In talks of .NET 3.5 SP1, Url Routing is even ignored some of the time … and if it’s not ignored, it’s barely mentioned in passing. And then there’s the documentation which is, IMHO, pretty lame and the “How To” articles on MSDN are only mildly better.

In spite of (or maybe because of) that, I found myself intrigued by the whole idea of Url Routing. Yes, I had seen it and tweaked it in MVC and Dynamic Data, but I knew that there had to be additional uses for it. So … I set about to build a demo for Houston Tech Fest that showed how to get started with Url Routing, adding it to an existing website that showed some data from Northwind. It’s not a pretty or even really functional app … that’s not the point … and has Url’s that are both plain vanilla and that require query strings. In addition, there was absolutely, positively no consistency between the pages or the query string parameters. I know that doesn’t happen in the real world! ;-)

There was one thing that I did do in the application that I don’t see done frequently; I added a static utility class that built and returned Url’s for the different pages in the app. Again, not something that I typically see, but it is definitely something that can make the maintenance of links and url’s easier and more elegant. Well, maybe not elegant but it sure beats “Find in project”. But then, url’s in the application never change, do they?

If you’re interested, you can find the PPT and the demos (as well as something resembling a script) on my SkyDrive. It’s the same place as the link that I posted the other day for my Houston TechFest presentations. However, I wanted to spend a little more time and explain what’s going on and how it all works.

StaticRoutingHandler

This is what does the work. All you have to do to create a routing handler is to implement the IRouteHandler interface. It’s pretty a simple interface – there’s only 1 method. This takes a url route and then transfers this to a static .aspx page that is configured for the route (1 route, 1 page). Since I needed to pass query strings to the pages, I also do some url rewriting in here. Technically, this is not necessary to pass information to the target page, but remember – I didn’t want to have to spend a lot of time making changes all over the app and changing the pages to no longer require query strings would be more work and get away from the point of the demo (i.e. making it easy). While you do create an instance of the page with Url Routing, the pages in this demo didn’t have any nice properties that encapsulated the query string parameters. No way to do that without url rewriting when the app is expecting to get query strings. It takes the data arguments specified in the route and turns them into query strings, using the name as the query string parameter. Here it is:

public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
    string finalPath = VirtualPath;
    if (requestContext.RouteData.Values.Count > 0)
    {
        List<string> values = new List<string>();
        //Add these to the virtual path as QS arguments
        foreach (var item in requestContext.RouteData.Values)
        {
            values.Add(item.Key + "=" + item.Value);
        }
        finalPath += "?" + String.Join("&", values.ToArray());

    }

    //Rewrite the path to pass the query string values. 
    HttpContext.Current.RewritePath(finalPath);

    var page = BuildManager.CreateInstanceFromVirtualPath(
        VirtualPath, typeof(Page)) as IHttpHandler;
    return page;
}

Configuration

This is where it could potentially get hairy. With MVC and Dynamic Data, it’s pretty easy to do it in the global.asax file since their paths and page names follow a clear and simple convention. Not so with the sample app. So each route/page combination needs to be registered separately because the page names have absolutely no consistency, not to mention the query string arguments. Ewwww … that’ll bloat your global.asax real quick. Since I didn’t like how that was going, I decided that I’d make it configuration-driven. This had the added benefit of allowing you to change the routes, arguments, etc. without redeploying code. I wrote a custom configuration section to handle this; this also makes the config read/write with the API which I thought might be a nice-to-have. So, the section looks like the following:

<StaticRoutes>
  <Routes>
    <add name="AXD" routeUrl="{resource}.axd/{*pathInfo}"></add>
    <add name="CategoryList" routeUrl ="CategoryList" virtualPath="~/CategoryList.aspx"/>
    <add name="ProductList" routeUrl="Category/Products/{C}" virtualPath="~/ProductList.aspx">
      <Restrictions>
        <add name="C" value="\d"/>
      </Restrictions>
    </add>
    <add name="ViewProduct" routeUrl="Product/{Id}" virtualPath="~/ViewProduct.aspx">
      <Restrictions>
        <add name="Id" value="\d"></add>
      </Restrictions>
      <Defaults>
        <add name="Id" value="1"/>
      </Defaults>
    </add>
    <add name="CustomerOrders" routeUrl="Customers/Orders/{Cu}" virtualPath="~/ListOrders.aspx">
    </add>
    <add name="CustomerList" routeUrl="Customers" virtualPath="~/Customers.aspx">
    </add>
    <add name="OrderDetails" routeUrl="Customers/Orders/{Id}" virtualPath="~/OrderDetails.aspx"/>
  </Routes>
</StaticRoutes>

It’s got all of the information that we need to create our routes. Restrictions and Defaults are optional – not every route needs them. You’ll also notice that the “AXD” route doesn’t have any virtual path listed … when there is no virtualPath specified, the StaticRoutingHandler.Configure method will add a StopRoutingHandler rather than the StaticRoutingHandler. The StopRoutingHandler is the only handler (that I could find) that is in the API itself (MVC and Dynamic Data each have their own RoutingHandlers). It tells Routing to simply ignore the request and send it along it’s merry way as if there was no routing configured. The order of the routes in the config file does matter, but that has nothing to do with my code; when the ASP.NET routing framework looks for the handler that a particular url matches, it grabs the first that it finds on the list. So … that’s how you prioritize your routes. The query string parameters are surrounded with curly braces … so “{Cu}” in the CustomerOrders route above would get put in as a query string named “Cu” with the value that appears in that place in the url.

With the configuration, RegisterRoutes, rather than being a mess that looks more like my office desk than code, is clean, simple and sweet. We just need to call a static method on the StaticRoutingHandler class to read the configuration and add the routes.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.Clear();

    StaticRouting.StaticRoutingHandler.Configure(routes);

}

The names (which are required) also allow us to build the Url using the Routing API, rather than having Url’s hardcoded in the application. It’s pretty simple and straightforward; below is one of the more “complex” examples as it builds the RouteValueDictionary with the value for the query string.

public static string GetProductsForCategory(int categoryId)
{

    var values = new RouteValueDictionary();
    values.Add("C", categoryId);

    var path = RouteTable.Routes.GetVirtualPath(
        null,
        "ProductList",
        values);
    return path.VirtualPath;

}

I got a question when I did this presentation about wildcard parameters. I knew about them and how they worked, but somehow didn’t think to play with them in this sample app. First, you can do this (and I mentioned it in the presentation) by adding a wildcard parameter to the route. In our configuration, it would look like the following:

<add name="Wildcard" routeUrl ="Wildcard/{id}/{* params}" virtualPath="~/Customers.aspx"></add> 

It doesn’t have to have a set parameter (id in this case) in the routeUrl; I just put that there as a comparison. Everything else after that goes into a single item called (in this case) “params”. The “id” is exactly as we expect it to be. However, the wildcard doesn’t translate well into query string parameters. Yes, it is named “params”, but the value is everything in the path in the wildcard’s place, including the slashes in the path. So, with the url http://localhost/Wildcard/3/sasdf/asdf/as, the value of “params” is sasdf/asdf/as. Yes, the routing handler will very happily pass this along when it rewrites the url, but doesn’t really seem to make sense in this context. In this case, I’d say that you put each possible query string/route combination in as a different route to make sure that the page gets the parameters it expects the way that it expects them. I might, some time in the future, put some more thought into this and come up with something else, but for right now, I’m happy punting on it for now and just adding a route for each combination.



Tags: ,

Web (and ASP.NET) Stuff

ASP.NET Async Page Model

October 1, 2008 7:07 PM

I just did a Code Clinic for the Second Life .NET User’s Group on using the ASP.NET async page model and it occurred to me that it’d be a good idea to do a little blog post about it as well. I’ve noticed that a lot of developers don’t know about this little feature and therefore don’t use it. It doesn’t help that the situations where this technique helps aren’t readily apparent with functional testing on the developer’s workstation or even on a separate test server. It only rears its head if you do load testing … something that few actually do (I won’t go there right now).

So, let me get one thing straight from the get-go here: I’m not going to be talking about ASP.NET AJAX. No way, no how. I’m going to be talking about a technique that was in the original release of ASP.NET 2.0 and, of course, it’s still there. There are some big-time differences between the async model and AJAX. First, the async model has nothing at all to do with improving the client experience (at least not directly, though it will tend to). Second, the async model doesn’t have any client-side goo; it’s all server-side code. And finally, there is no magic control that you just drop on your page to make it work … it’s all code that you write in the code-behind page. I do want to make sure that this clear ‘cuz these days when folks see “async” in relation to web pages, they automatically think AJAX. AJAX is really a client-side technique, not server side. It does little to nothing to help your server actually scale … it can, in some cases, actually have a negative impact. This would happen when you make additional round trips with AJAX that you might not normally do without AJAX, placing additional load on the server. Now, I’m not saying that you shouldn’t use AJAX … it’s all goodness … but I just want to clarify that this isn’t AJAX. Now, you can potentially this this for AJAX requests that are being processed asynchronously from the client.

Now that we have that out of the way, let me, for a moment, talk about what it is. First, it’s a really excellent way to help your site scale, especially when you have long-running, blocking requests somewhere in the site (and many sites do have at least a couple of these). Pages that take a few seconds or more to load may be good candidates. Processes like making web services calls (for example, to do credit card processing and order placement on an eCommerce site) are excellent candidates as well.

Why is this such goodness? It has to do with the way ASP.NET and IIS do page processing. ASP.NET creates a pool of threads to actually do the processing of the pages and there is a finite number of threads that will be added to the pool. These processing threads are created as they are needed … so creating additional threads will incur some overhead and there is, of course, overhead involved with the threads themselves even after creation. Now, when a page is requested, a thread is assigned to the page from the pool and that thread is then tied to processing that page and that page alone … until the page is done executing. Requests that cannot be serviced at the time of the request are then queued for processing as a thread becomes available. So … it then (logically) follows that pages that take a long time and consume a processing thread for extended periods will affect the scalability of the site. More pages will wind up in the queue and will therefore take longer since they are waiting for a free thread to execute the page. Of course, once the execution starts, it’ll have no difference on the performance … it’s all in the waiting for a thread to actually process the page. The end result is that you cannot services as many simultaneous requests and users.

The async page model fixes this. What happens is that the long running task is executed in the background. Once the task is kicked off, the thread processing the thread is then free to process additional requests. This results in a smaller queue and less time that a request waits to be serviced. This means more pages can actually be handled at the same time more efficiently … better scalability. You can see some test results of this on Fritz Onion’s blog. It’s pretty impressive. I’ve not done my own scalability testing on one of my test servers here, but I think, shortly, I will. Once I do, I’ll post the results here.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

How do you do this? To get started is actually quite easy, simple in fact. You need to add a page directive to your page. This is required regardless of which method you use (there are two). ASP.NET will then implement IAsyncHttpHandler for you behind the scenes. It looks like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Async="True" %>

Simple enough, right? Let me just add a couple of things that you need to make sure you have in place. You will need to follow the .NET asynchronous pattern for this to work … a Begin method that returns IAsyncResult and an end method that takes this result. It’s typically easiest to do this with API’s that already have this implemented for you (you just return their IAsyncResult object). There’s a ton of them and they cover most of the situations where this technique helps.

Now, to actually do this. Like I said, there’s two different ways to use this. The first is pretty easy to wireup and you can add multiple requests (I misstated this during the Code Clinic), but all of the async requests run one at a time, not in parallel. You simply call Page.AddOnPreRenderCompleteAsync and away you go. There are two overloads for this method, as follows:

void AddOnPreRenderCompleteAsync(BeginEventHandler b, EndEventHandler e)

 

 

 

 

 

 

 

 

 

 

 

 

void AddOnPreRenderCompleteAsync(BeginEventHandler b, EndEventHandler e, object state)

The handlers look like the following:

IAsyncResult BeginAsyncRequest(object sender, EventArgs e, AsyncCallback cb, object state)
void EndAsyncRequest(IAsyncResult ar)

 

 

 

 

 

 

The state parameter can be used to pass any additional information/object/etc. that you would like to the begin and the end methods (it’s a member if the IAsyncResult interface), so that can be pretty handy.

The code behind for such a page would look like the following:

    protected void Page_Load(object sender, EventArgs e)
    {
        LoadThread.Text = 
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginGetMSDN),
            new EndEventHandler(EndAsyncOperation)); 

    }

    public IAsyncResult BeginGetMSDN(object sender, EventArgs e, AsyncCallback cb, object state)
    {
        BeginThread.Text =
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        HttpWebRequest  _request = 
            (HttpWebRequest)WebRequest.Create(@"http://msdn.microsoft.com");
        return _request.BeginGetResponse(cb, _request);
    }

    void EndAsyncOperation(IAsyncResult ar)
    {
        EndThread.Text =
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        string text;
        HttpWebRequest _request = (HttpWebRequest)ar.AsyncState;
        using (WebResponse response = _request.EndGetResponse(ar))
        {
            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                text = reader.ReadToEnd();
            }
        }

        Regex regex = new Regex("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase);
        MatchCollection matches = regex.Matches(text);

        StringBuilder builder = new StringBuilder(1024);
        foreach (Match match in matches)
        {
            builder.Append(match.Groups[1]);
            builder.Append("<br/>");
        }

        Output.Text = builder.ToString();
    }

}

If you run this (on a page with the proper controls, of course), you will notice that Page_Load and BeginGetMSDN both run on the same thread while EndAsyncOperation runs on a different thread.

The other method uses a class called PageAsyncTask to register an async task with the page. Now, with this one, you can actually execute multiple tasks in parallel so, in some cases, this may actually improve the performance of an individual page. You have two constructors for this class:

 

 

public PageAsyncTask(
        BeginEventHandler beginHandler,
        EndEventHandler endHandler,
        EndEventHandler timeoutHandler,
        Object state)

and

public PageAsyncTask(
    BeginEventHandler beginHandler,
    EndEventHandler endHandler,
    EndEventHandler timeoutHandler,
    Object state,
    bool executeInParallel){}

 

The only difference between the two is that one little argument … ExecuteInParallel. The default for this is false, so if you want your tasks to execute in parallel, you need to use the second constructor. The delegates have identical signatures to the delegates for AddOnPreRenderComplete. The new handler timeoutHandler, is called when the operations times out and has the same signature to the end handler. So … it’s actually trivial to switch between the two (I did it to the sample listing above in about a minute.) I, personally, like this method better for two reasons. One, the cleaner handling of the timeout. That’s all goodness to me. Second, the option to have them execute in parallel. The same page as above, now using PageAsyncTask looks like to following:

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        LoadThread.Text = 
            Thread.CurrentThread.ManagedThreadId.ToString();
        
        PageAsyncTask t = new PageAsyncTask(
            BeginGetMSDN,
            EndAsyncOperation,
            AsyncOperationTimeout,
            false);
    }

    public IAsyncResult BeginGetMSDN(object sender, EventArgs e, AsyncCallback cb, object state)
    {
        BeginThread.Text =
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        HttpWebRequest  _request = 
            (HttpWebRequest)WebRequest.Create(@"http://msdn.microsoft.com");
        return _request.BeginGetResponse(cb, _request);
    }

    void EndAsyncOperation(IAsyncResult ar)
    {
        EndThread.Text =
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        string text;
        HttpWebRequest _request = (HttpWebRequest)ar.AsyncState;
        using (WebResponse response = _request.EndGetResponse(ar))
        {
            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                text = reader.ReadToEnd();
            }
        }

        Regex regex = new Regex("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase);
        MatchCollection matches = regex.Matches(text);

        StringBuilder builder = new StringBuilder(1024);
        foreach (Match match in matches)
        {
            builder.Append(match.Groups[1]);
            builder.Append("<br/>");
        }

        Output.Text = builder.ToString();
    }

    void AsyncOperationTimeout(IAsyncResult ar)
    {
        EndThread.Text = Thread.CurrentThread.ManagedThreadId.ToString(); 
        Output.Text = "The data is not currently available. Please try again later."
    }

}

Not much difference there. We have 1 additional method for the timeout and the registration is a little different. By the way, you can pass null in for the timeout handler if you don’t care about it. I don’t recommend doing that, personally, but that’s up to you.

There you have it … a quick tour through the ASP.NET asynchronous page model. It’s clean, it’s easy, it’s MUCH better than spinning up your own threads and messing with synchronization primitives (this is mucho-bad-mojo, just say NO) and it’s got some pretty significant benefits for scalability.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

With that, I’m outta here. Happy coding!



Cool way to do ASP.NET Caching with Linq

July 11, 2008 8:18 PM

OK, well, I think it's cool (and since the mind is its own place ...). I've been a big fan of ASP.net's cache API since I found out it way back in the 1.0 beta. It certainly solves something that was problematic in ASP "Classic" in a clean, elegant and darn easy to use way. Unfortunately, not a lot of folks seem to know about it. So I'll start with a little overview of ASP.net caching.

As the name implies, it's a cache that sits server side. All of the relevant, .Net-supplied classes are in the System.Web.Caching namespace and the class representing the cache itself is System.Web.Caching.Cache. You can access it from the current HttpContext (which you'll see). The management of the cache is handled completely by ASP.net ... you just have to add objects to it and then read from it. When you add to the cache, you can set options like dependencies, expiration, priority and a delegate to call when the item is removed from the cache. Dependencies are interesting ... they will automatically invalidate (and remove) the cache item based on notification from the dependency. ASP.net 1.x had only 1 cache dependency class (System.Web.Caching.CacheDependency) that allowed you to have a dependency on a file, another cache item, and array of them or another CacheDependency. Framework 2.0 introduced System.Web.Caching.SqlCacheDependency for database dependencies and System.Web.Caching.AggregateCacheDependency for multiple, related dependencies. With the AggregateCacheDependency, if one of the dependencies changes, it item is invalidated and tossed from the cache. Framework 2.0 also (finally) "unsealed" the CacheDependency class, so you could create your own cache dependencies. With expiration, you can have an absolute expiration (specific time) or a sliding expiration (TimeSpan after last access). Priority plays into the clean-up algorithm; the Cache will remove items that haven't expired if the cache taking up too much memory/resources. Items with a lower priority are evicted first. Do yourself a favor and make sure that you keep your cache items reasonable. Your AppDomain will thank you for it.

ASP.net also provides page and partial-page caching mechanisms. That, however, is out of our scope here. For the adventurous among that don't know what that is ...

So ... the cache ... mmmmm ... yummy ... gooooood. It's golly-gee-gosh-darn useful for items that you need on the site, but don't change often. Those pesky drop-down lookup lists that come from the database are begging to be cached. It takes a load off the database and is a good way to help scalability - at the cost of server memory, of course. (There ain't no free lunch.) Still, I'm a big fan of appropriate caching.

So ... what's the technique I mentioned that this post is title after? Well, it's actually quite simple. It allows you to have 1 single common method to add and retrieve items from the cache ... any Linq item, in fact. You don't need to know anything about the cache ... just the type that you want and the DataContext that it comes from. And yes, it's one method to rule them all, suing generics (generics are kewl!) and the Black Voodoo Majick goo. From there, you can either call it directly from a page or (my preferred method) write a one-line method that acts as a wrapper. The returned objects are detached from the DataContext before they are handed back (so the DataContext doesn't need to be kept open all) and returned as a generic list object. The cache items are keyed by the type name of the DataContext and the object/table so that it's actually possible to have the same LinqToSql object come from two different DataContexts and cache both of them. While you can load up the cache on application start up, I don't like doing that ... it really is a killer for the app start time. I like to lazy load on demand. (And I don't wanna hear any comments about the lazy.)

Here's the C# code:

/// <summary>
/// Handles retrieving and populating Linq objects in the ASP.NET cache
/// </summary>
/// <typeparam name="LinqContext">The DataContext that the object will be retrieved from.</typeparam>
/// <typeparam name="LinqObject">The object that will be returned to be cached as a collection.</typeparam>
/// <returns>Generic list with the objects</returns>
public static List<LinqObject> GetCacheItem<LinqContext, LinqObject>()
    where LinqObject : class
    where LinqContext : System.Data.Linq.DataContext, new()
{
    //Build the cache item name. Tied to context and the object.
    string cacheItemName = typeof(LinqObject).ToString() + "_" + typeof(LinqContext).ToString();
    //Check to see if they are in the cache. 
    List<LinqObject> cacheItems = HttpContext.Current.Cache[cacheItemName] as List<LinqObject>;
    if (cacheItems == null)
    {
        //It's not in the cache -or- is the wrong type. 
        //Create a new list.
        cacheItems = new List<LinqObject>();
        //Create the contect in a using{} block to ensure cleanup. 
        using (LinqContext dc = new LinqContext())
        {
            try
            {
                //Get the table with the object from the data context.
                System.Data.Linq.Table<LinqObject> table = dc.GetTable<LinqObject>();
                //Add to the generic list. Detaches from the data context. 
                cacheItems.AddRange(table);
                //Add to the cache. No absolute expirate and a 60 minute sliding expiration
                HttpContext.Current.Cache.Add(cacheItemName, cacheItems, null,
                    System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(60),
                    System.Web.Caching.CacheItemPriority.Normal, null);
            }
            catch (Exception ex)
            {
                //Something bad happened.
                throw new ApplicationException("Could not retrieve the request cache object", ex);
            }
        }
    }
    //return ... 
    return cacheItems;
}

And in VB (see, I am multi-lingual!) ...

''' <summary>
''' Handles retrieving and populating Linq objects in the ASP.NET cache
''' </summary>
''' <typeparam name="LinqContext">The DataContext that the object will be retrieved from.</typeparam>
''' <typeparam name="LinqObject">The object that will be returned to be cached as a collection.</typeparam>
''' <returns>Generic list with the objects</returns>
Public Shared Function GetCacheItem(Of LinqContext As {DataContext, New}, LinqObject As Class)() As List(Of LinqObject)
    Dim cacheItems As List(Of LinqObject)

    'Build the cache item name. Tied to context and the object.
    Dim cacheItemName As String = GetType(LinqObject).ToString() + "_" + GetType(LinqContext).ToString()
    'Check to see if they are in the cache. 
    Dim cacheObject As Object = HttpContext.Current.Cache(cacheItemName)

    'Check to make sure it's the correct type.
    If cacheObject.GetType() Is GetType(List(Of LinqObject)) Then
        cacheItems = CType(HttpContext.Current.Cache(cacheItemName), List(Of LinqObject))
    End If

    If cacheItems Is Nothing Then
        'It's not in the cache -or- is the wrong type. 
        'Create a new list.
        cacheItems = New List(Of LinqObject)()
        'Create the contect in a using   block to ensure cleanup. 
        Using dc As LinqContext = New LinqContext()
            Try
                'Get the table with the object from the data context.
                Dim table As Linq.Table(Of LinqObject) = dc.GetTable(Of LinqObject)()
                'Add to the generic list. Detaches from the data context. 
                cacheItems.AddRange(table)
                'Add to the cache. No absolute expirate and a 60 minute sliding expiration
                HttpContext.Current.Cache.Add(cacheItemName, cacheItems, Nothing, _
                    Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(60), _
                   CacheItemPriority.Normal, Nothing)
            Catch ex As Exception
                'Something bad happened.
                Throw New ApplicationException("Could not retrieve the request cache object", ex)
            End Try
        End Using
    End If

    'return ... 
    Return cacheItems
End Function

 

The comments, I think, pretty much say it all. It is a static method (and the class is a static class) because it's not using any private fields (variables). This does help performance a little bit and, really, there is no reason to instantiate a class if it's not using any state. Also, note the generic constraints - these are actually necessary and make sure that we aren't handed something funky that won't work. These constraints are checked and enforced by the compiler.

Using this to retrieve cache items is now quite trivial. The next example shows a wrapper function for an item from the AdventureWorks database. I made it a property but it could just as easily be a method. We won't get into choosing one over the other; that gets religious.

public static List<StateProvince> StateProvinceList
{
    get
    {
        return GetCacheItem<AdvWorksDataContext, StateProvince>(); 
    }
}

And VB ...

Public ReadOnly Property StateProvinceList() As List(Of StateProvince)
    Get
        Return GetCacheItem(Of AdvWorksDataContext, StateProvince)()
    End Get
End Property

Isn't that simple? Now, if you only have one DataContext type, you can safely code that type into the code instead of taking it as a generic. However, looking at this, you have to admit ... you can use this in any ASP.net project where you are using Linq to handle the cache. I think it's gonna go into my personal shared library of tricks.

As I think you can tell, I'm feeling a little snarky. It's Friday afternoon so I have an excuse. BTW ... bonus points to whoever can send me an email naming the lit reference (and finish it!) in this entry. Umm, no it isn't Lord of the Rings.



Tags: , , ,

.NET Stuff | Linq | Web (and ASP.NET) Stuff

Thoughts on Secure File Downloads

June 12, 2008 3:45 PM

Well, that’s kinda over-simplifying it a bit. It’s more about file downloads and protecting files from folks that shouldn’t see them and comes from some of the discussion last night at the OWASP User Group. So … I was thinking that I’d put a master file-download page for my file repository. The idea around it is that there would be an admin section where I could upload the files, a process that would also put them into the database with the relevant information (name, content type, etc.). This would be an example of one of the vulnerabilities discussed last night … insecure direct object reference. Rather than giving out filenames, etc., it would be a file identifier (OWASP #4). That way, there is no direct object reference. That file id would be handed off to a handler (ASHX) that would actually send the file to the client (just doing a redirect from the handler doesn’t solve the issue at all).

But I got to thinking … I might also want to limit access to some files to specific users/logins. So now we are getting into restricting URL access (OWASP #10). If I use the same handler as mentioned above, I can’t use ASP.NET to restrict access, leaving me vulnerable. Certainly, using GUIDs makes them harder to guess, but it won’t prevent UserA, who has access to FileA, sending a link to UserB, who does not have access to FileA.  However, once UserB logged in, there would be nothing to prevent him/her from getting to the file … there is no additional protection above and beyond the indirect object reference and I’m not adequately protecting URL access.

This highlights one of the discussion points last night – vulnerabilities often travel in packs. We may look at things like the OWASP Top Ten and identify individual vulnerabilities, but that looks at the issues in isolation. The reality is that you will often have a threat with multiple potential attack vectors from different vulnerabilities. Or you may have a vulnerability that is used to exploit another vulnerability (for example, a Cross-Site Scripting vulnerability that is used to exploit a Cross Site Request Forgery vulnerability and so on and so on).

So … what do I do here? Well, I could just not worry about it … the damage potential and level of risk is pretty low but that really just evades the question. It’s much more fun to actually attack this head on and come up with something that mitigates the threat. One method is to have different d/l pages for each role and then protect access to those pages in the web.config file. That would work, but it’s not an ideal solution. When coming up with mitigation strategies, we should also keep usability in mind and to balance usability with our mitigation strategy. This may not be ideal to the purist, but the reality is that we do need to take things like usability and end-user experience into account. Of course, there’s also the additional maintenance that the “simple” method would entail as well – something I’m not really interested in. Our ideal scenario would have 1 download page that would then display the files available to the user based on their identity, whether that is anonymous or authenticated.

So … let’s go through how to implement this in a way that mitigates (note … not eliminates but mitigates) the threats.

First, the database. Here’s a diagram:

FileListDb

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

We have the primary table (FileList) and then the FileListXREF table. The second has the file ids and the roles that are allowed to access the file. A file that all are allowed to access will not have any records in this table.

To display this list of files for a logged in user, we need to build the Sql statement dynamically, with a where clause based on the roles for the current user. This, by the way, is one of the “excuses” that I’ve heard about using string concatenation for building Sql statements. It’s not a valid one, it just takes some more. And, because we aren’t using concatenation, we’ve also mitigated Sql injection, even though the risk of that is low since the list of roles is coming from a trusted source. Still, it’s easy and it’s better to be safe. So … here’s the code.

public static DataTable GetFilesForCurrentUser()
{
    //We'll need this later. 
    List<SqlParameter> paramList =
        new List<SqlParameter>();
    //Add the base Sql.
    //This includes the "Where" for files for anon users
    StringBuilder sql = new StringBuilder(
        "SELECT * FROM FileList " +
        "WHERE (FileId NOT IN " +
        "(SELECT FileId FROM FileRoleXREF))");
    //Check the user ... 
    IPrincipal crntUser = HttpContext.Current.User;
    if (crntUser.Identity.IsAuthenticated)
    {
        string[] paramNames = GetRoleParamsForUser(paramList, crntUser);
        //Now add to the Sql
        sql.Append(" OR (FileId IN (SELECT FileId FROM " +
            "FileRoleXREF WHERE RoleName IN (");
        sql.Append(String.Join(",", paramNames));
        sql.Append(")))");

    }
    return GetDataTable(sql.ToString(), paramList);
}

private static string[] GetRoleParamsForUser(List<SqlParameter> paramList, IPrincipal crntUser)
{
    //Now, add the select for the roles. 
    string[] roleList =
        Roles.GetRolesForUser(crntUser.Identity.Name);
    //Create the parameters for the roles
    string[] paramNames = new string[roleList.Length];
    for (int i = 0; i < roleList.Length; i++)
    {
        string role = roleList[i];
        //Each role is a parameter ... 
        string paramName = "@role" + i.ToString();
        paramList.Add(new SqlParameter(paramName, role));
        paramNames[i] = paramName;
    }
    return paramNames;
}

From there, creating the command and filling the DataTable is simple enough. I’ll leave that as an exercise for the reader.

This still, however, doesn’t protect us from the failure to restrict URL access issue mentioned above. True, UserA only sees the files that he has access to and UserB only sees the files that she has access to. But that’s still not stopping UserA from sending UserB a link to a file that he can access, but she can’t. In order to prevent this, we have to add some additional checking into the ASHX file to validate access. It’d be easy enough to do it with a couple of calls to Sql, but here’s how I do it with a single call …

public static bool UserHasAccess(Guid FileId)
{
    //We'll need this later. 
    List<SqlParameter> paramList =
        new List<SqlParameter>();
    //Add the file id parameter
    paramList.Add(new SqlParameter("@fileId", FileId));
    //Add the base Sql.
    //This includes the "Where" for files for anon users
    StringBuilder sql = new StringBuilder(
        "SELECT A.RoleEntries, B.EntriesForRole " +
        "FROM (SELECT COUNT(*) AS RoleEntries " +
        "FROM FileRoleXREF X1 " +
        "WHERE (FileId = @fileId)) AS A CROSS JOIN ");
    //Check the user ... 
    IPrincipal crntUser = HttpContext.Current.User;
    if (crntUser.Identity.IsAuthenticated)
    {
        sql.Append("(SELECT Count(*) AS EntriesForRole " +
            "FROM FileRoleXREF AS X2 " +
            "WHERE (FileId = @fileId) AND " +
            "RoleName IN (");

        string[] roleList = GetRoleParamsForUser(paramList, crntUser);
        sql.Append(String.Join(",", roleList));
        sql.Append(")) B");
    }
    else
    {
        sql.Append("(SELECT 0 AS EntriesForRole) B"); 
    }
    DataTable check = GetDataTable(sql.ToString(), paramList);
    if ((int)check.Rows[0]["RoleEntries"] == 0) //Anon Access
        {return true;}
    else if ((int)check.Rows[0]["EntriesForRole"] > 0)
        {return true;}
    else
        {return false;}
}

So, this little check before having the handler stream the file to the user makes sure that someone isn’t getting access via URL to something that they shouldn’t have access to. We’ve also added code to ensure that we mitigate any Sql injection errors.

Now, I’ve not gotten everything put together in a “full blown usable application”. But … I wanted to show some of the thought process around securing a relatively simple piece of functionality such as this. A bit of creativity in the process is also necessary … you have to think outside the use case, go off the “happy path” to identify attack vectors and the threats represented by the attack vectors.



Tags: , , ,

.NET Stuff | Security | Web (and ASP.NET) Stuff

Fixing my instance of dasBlog

May 28, 2008 7:17 PM

Well, I finally have the remaining issues that I had with my dasBlog installation fixed - or at least mostly so. Of course, now I see that there is a new build out on CodePlex (mostly bug fixes) but I'm not going to bother with it at this point in time.

Still, in digging around in dasBlog, I've found a couple of things that I stumbled over that are (I feel) are issues. I'm not sure that I'd put them in a bugs per se as they don't break the application, but I do thing that they oughta be fixed. Don't get me wrong - dasBlog is a great blogging tool and pretty darn solid. I don't know if they've been fixed in the current release, but I'm gonna log them up there as well.  Both of these are in

  • Exceptions for flow control: This is is newtelligence.dasBlog.Runtime.LoggingDataServiceXml::LoggingDataService.Xml. This method appears to be using exception handling for flow control. Here's what's going on: when you load up the logs for a particular day, it goes to get those services from the file system (the default location is under the Logs folder of the root web). First it checks for archived (zipped) files ... and it does this right by checking for file existence. But ... when it looks for files in the Xml format, it doesn't check for the file existence. It just tries to open it, assuming that the file is there. Well, it may not be ... if the date is in the future or there are no logs for it (i.e. you tried to get the logs for pre-dasBlog), it throws an FileNotFoundException. This is then caught by the generic application exception handler. The simple solution is to use a wrap the using block for the new StreamReader with an if block that checks File.Exists(path).
  • newtelligence.dasBlog.Runtime.LoggingDataServiceXml.ArchiveLogFiles: This is using AppPool.QueueUserWorkItem for multi-threading. This isn't really ideal for multi-threading in a web application ... you really should use the page async model. Though ... I'm scratching my head as to the correct way to implement this. This is called by the WriteEvent method of the LoggingDataService ... so it's not something that you can really put into a PageAsyncTask. Doing so would, it seems, involved a change to the admin pages to call this on demand from the admin UI. That, however, may also violate the source independence of the archive provider.

These aren't, by any stretch of the imagination, show stoppers. The exception handling one, though, really gets under my skin. Exception handling for flow control - and I'm calling it that, though really to do so is something of a stretch because it's only caught by the application's generic handler - is pretty bad mojo. And it's expensive. Much more expensive than a simple check. Just do an MSN Search for Exception Flow Control. It's all over and it's not language-specific. Fortunately, in this case, the fix is a simple one.

I do have a little beef too, though and that's with the reporting. Daily reports into your email are good - don't get me wrong - but it'd be nice to see aggregated reports across a week, a month or more.

And yes, before you ask, I've also applied to join the team so that I can fix things and add new stuff rather than sitting here and complaining about it.



Tags: ,

.NET Stuff | Web (and ASP.NET) Stuff