Sunday, July 26, 2009
Sunday, July 26, 2009 7:35:50 AM (GMT Daylight Time, UTC+01:00) (ASP.Net | C#)

Earlier I posted about a compression filter for ASP.NET MVC that was the product of two other posts on compression and QValues. I’ve also seen a couple of posts describing different approaches to creating syndication content via ASP.NET MVC – RSS or Atom feeds. Here are two suggestions – one at DeveloperZen and the other at Stack Overflow that both use an RssActionResult subclass of ActionResult.

What was missing was a 304 Not Modified filter – so that feeds that have not changed can return a 304 Not Modified HTTP Response - saving bandwidth and improving performance of the consumer.

Here’s my attempt at putting it all together using a custom NotModifiedFilter, the CompressionFilter and a SyndicationActionResult class.

First the NotModifiedFilter. This article at the Embarcadero Developer Network was very helpful (along with the W3C standard for 304 Not Modified).

Here’s the filter.

public class NotModifiedFilterAttribute : ActionFilterAttribute
{
    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        var response = filterContext.HttpContext.Response;
        var request = filterContext.HttpContext.Request;           

        if ((IsSourceModified(request, response) == false))
        {
            response.SuppressContent = true;
            response.StatusCode = 304;
            response.StatusDescription = "Not Modified";
            // Explicitly set the Content-Length header so the client doesn't wait for
            // content but keeps the connection open for other requests
            response.AddHeader("Content-Length", "0");
        }
    }
}

And here’s the helper method that tests for modified content.

private static bool IsSourceModified(HttpRequestBase request, HttpResponseBase response)
{                  
    bool dateModified = false;
    bool eTagModified = false;

    string requestETagHeader = request.Headers["If-None-Match"] ?? string.Empty;
    string requestIfModifiedSinceHeader = request.Headers["If-Modified-Since"] ?? string.Empty;            
    DateTime requestIfModifiedSince;                    
    DateTime.TryParse(requestIfModifiedSinceHeader, out requestIfModifiedSince);
    
    string responseETagHeader = response.Headers["ETag"] ?? string.Empty;
    string responseLastModifiedHeader = response.Headers["Last-Modified"] ?? string.Empty;
    DateTime responseLastModified;
    DateTime.TryParse(responseLastModifiedHeader, out responseLastModified);

    if (requestIfModifiedSince != DateTime.MinValue && responseLastModified != DateTime.MinValue)
    {
        if (responseLastModified > requestIfModifiedSince)
        {
            TimeSpan diff = responseLastModified - requestIfModifiedSince;
            if (diff > TimeSpan.FromSeconds(1))
            {
                dateModified = true;
            }
        }
    }
    else
    {
        dateModified = true;
    }

    //Leave the default for eTagModified = false so that if we
    //don't get an ETag from the server we will rely on the fileDateModified only           
    if (String.IsNullOrEmpty(responseETagHeader) == false)
    {
        eTagModified = responseETagHeader.Equals(requestETagHeader, StringComparison.Ordinal) == false;
    }

    return (dateModified || eTagModified);    
}

And lastly – here’s my SyndicationActionResult class which uses a Func<FeedData> delegate to get the feed data – and so can be used for any type of feed – Rss, Atom, Sitemaps etc. (FeedData contains the feed content as well as the last modified date and the ETag). Not sure if I’m guilty of cargo cult programming here since I was a little unsure about setting the ‘Last-Modified’ header manually instead of using response.Cache.SetLastModified() method. However the latter was not showing up in the HttpContext of the OnResultExecuted method in the filter. Maybe the runtime moves this value into the headers collection later in the pipeline.

public class SyndicationActionResult : ActionResult
{
    public Func<FeedData> ActionDelegate { get; set; }
    
    public override void ExecuteResult(ControllerContext context)
    {
        if (ActionDelegate != null)
        {
            var response = context.HttpContext.Response;
            var data = ActionDelegate.Invoke() as FeedData;
            if (data != null)
            {
                response.ContentType = data.ContentType;
                response.AppendHeader("Cache-Control", "private");
                response.AppendHeader("Last-Modified", data.LastModifiedDate.ToString("r"));
                //response.Cache.SetLastModified(data.LastModifiedDate);
                response.AppendHeader("ETag", String.Format("\"{0}\"", data.ETag));
                response.Output.WriteLine(data.Content);
                response.StatusCode = 200;
                response.StatusDescription = "OK";
            }
        }
    }
}

Note that we write the content to the response.Output stream - however if the NotModifiedFilter tells us nothing has changed – then this will be suppressed – (although it is important that the ETag remains).

And very lastly - here's the Action in my SyndicationController class that puts it all together, and in this case – is used to provide the aggregate Atom feed on my home page at http://www.58bits.com.

[AcceptVerbs(HttpVerbs.Get)]
[NotModifiedFilter(Order = 1)]
[CompressFilter(Order = 2)]
public SyndicationActionResult SiteFeedAtom()
{
    return new SyndicationActionResult() { ActionDelegate = SyndicationHelper.GetAggregateAtomFeed };
}

As Scott sometimes says -  it may be  “poo”… or it maybe useful. At least it seems to be working ok for me, although suggestions on how any of it might be improved would be greatly appreciated.



| Comments [8] | | #  
Friday, July 17, 2009
Friday, July 17, 2009 5:13:03 PM (GMT Daylight Time, UTC+01:00) (Other Tech)

I’m still freak’n over how solid, fast and all-round great Windows 7 is. The first time ever an OS upgrade (without repaving the machine) actually feels like I did a fresh install.

Vista drove me nuts – both because of its sluggishness, and because of a totally failed file and folder management strategy. At the height of my frustration I was experimenting with Total Commander, Opus 9 and a couple of other file management applications in an effort to rid my self of the the ‘built in’ windows explorer. Folders did not remember the view or view preferences between sessions. Even worse was Vista’s attempt to guess the contents of a folder and ‘adapt’ accordingly. Maybe one day when we’re all in the cloud – or when the file system has been abstracted away by a content and keyword management system we won’t need conventional access to the file system per se – but we’re not there yet – and I do.

Here’s what they’ve done in Windows 7 – which is exactly right. They’ve separated their attempt at managing views based on content – from a regular file management window (and all explorer windows always remember the last view used).

So for example – if I’m looking at photos via the Picture Library – it looks like this.

Library

…with extra columns for tags and picture information. It’s a media specific view and that’s fine. Of course it could be a thumbnails view – but the important thing is that in details view – content specific columns appear.

If I need a ‘real’ file system window though – say because I want to quickly sort by file type or some other file system criteria – then I navigate to the same folder via the file system using a ‘regular’ explorer window (Windows Key – E) – which looks like this.

Folder

The default view for folders when you’ve entered them not via a Library but via the file system – is the ‘General Items’ view – which works like you’d expect it to when you need a ‘regular’ file management window.

What a relief. It works. It makes sense, and I can use Windows Explorer again.

Technorati Tags:


| Comments [0] | | #  
Saturday, July 11, 2009
Saturday, July 11, 2009 11:39:08 AM (GMT Daylight Time, UTC+01:00) (ASP.Net | CSS/XHTML)

UPDATE 12/07/2009 - As Shirley Boyle sang - "I dreamed a dream". The exercise below was great for learning how to use the IIS7 Url Rewrite Module - but there is a long list of user agents that contain the string "compatible; MSIE 6.0;" including the MSN spider and some transparent proxies. Internet Explorer 6 is the browser that refuses to die. :-(

ORIGINAL POST - This one took a little while to figure out along with a careful read of the reference docs for the IIS7 Url Rewrite Module. I was banging my head against the wall for a few minutes with {USER_AGENT} before reading the fine print… the HTTP prefix bit in particular.

All dash (“-”) symbols in the HTTP header name are converted to underscore symbols (“_”).
All letters in the HTTP header name are converted to capital case.
“HTTP_” prefix is added to the header name.
For example, in order to access the HTTP header “user-agent” from a rewrite rule, you can use the {HTTP_USER_AGENT} server variable.

And here’s the result…

<rule name="BlockIE6" stopProcessing="true">
  <match url=".*" />
  <conditions>
    <add input="{HTTP_USER_AGENT}" pattern="MSIE 6" />
    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
    <add input="{URL}" negate="true" pattern="ie6" />
  </conditions>
  <action type="Redirect" url="ie6" redirectType="Permanent" />
</rule>
It’s really really important that you do two things: 1) Prevent the redirect from redirecting static content – like script files and style sheets, ala the {REQUEST_FILENAME} negative condition, and 2) You also check the Url to see if it’s the redirected Url – i.e. they’ve already been redirected – otherwise you’ll get an endless redirect loop. In this case I’m sending them to the url “ie6” which is an action on a route in ASP.Net MVC – displaying a download page with some ‘alternatives’ :-)


| Comments [3] | | #  
Wednesday, July 08, 2009
Wednesday, July 08, 2009 7:09:39 PM (GMT Daylight Time, UTC+01:00) (General)

fsharpI recently watched Luca Bolognese’s presentation at last years PDC on F#. A really great presentation.

I’ve also been listening to Steve Vinoski’s podcasts on the The Functional Web.

There’s definitely stuff up in this area now. Coming from a non-comp-sci background – and only ever having written statically typed and imperative code (sad I know) – I’m curious to see where this will all lead. Haskell , Erlang, and Scala are all on the rise. CouchDB (backed I think by Lucene for search) and even Yaws (yet another web server) – are just two of several significant projects written in Erlang.

I think it was Martin Fowler who said that the best way to be a better programmer – is to learn another language, and F# is now on my list of things to do.

Technorati Tags: ,,,


| Comments [0] | | #  
Wednesday, July 08, 2009 4:47:00 PM (GMT Daylight Time, UTC+01:00) (General | Hardware)

What will those crazy folks at Apple think of next?

AGB_9599



| Comments [2] | | #  
Sunday, July 05, 2009
Sunday, July 05, 2009 6:43:41 AM (GMT Daylight Time, UTC+01:00) (Hardware)

2009-07-01_083947_270x355 I'm still in watch and wait mode - being torn between the iPhone 3GS and a BlackBerry device.

I partly want to own an iPhone just to be part of the iPhone phenomena - not to mention use the device that has totally changed the landscape of mobile communication.

I also really admire the BlackBerry devices. I noticed a few reviews for the new BlackBerry Tour - and thought 'oh that's nice'. Great specs - a refinement of the BlackBerry World Edition and Bold all rolled up into one - and then the bottom fell out. No WiFi. ? WTF? Here’s a good review of the Tour.

Still waiting and watching....

Technorati Tags: ,


| Comments [1] | | #  

search

categories

on this page

ads

archive

Total Posts: 97
This Year: 3
This Month: 0
This Week: 0
Comments: 92