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] | | #  
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] | | #  
Saturday, June 13, 2009
Saturday, June 13, 2009 1:58:35 AM (GMT Daylight Time, UTC+01:00) (ASP.Net | Enterprise)

Yet another great feature of IIS7  - declarative static content cache settings. It took a little detective work to find the settings for this feature – including a helpful post from Jörg Jooss.

I wanted to be able to set the client cache settings for the static content of a site I host at ORCS Web. In the past – unless offered by the control panel of your shared hosting provider – this would have meant emailing support and asking them to set the client cache header settings for the required directory in IIS6. In IIS7 this can be set now in the <system.webserver> section.

Note: If you’re going to try this on your local machine first, you will need to unlock the staticContent section in IIS7 (from the machine level) using the following appcmd…

appcmd unlock config /section:staticContent

You can also use appcmd to create the static content cache settings as shown in Jörg’s post above, which will add the correct section to your web.config – or you can directly edit/create the web.config file for the required directory.

I chose to hand edit the web.config – creating a new web.config located in the directory with the static content (i.e. images, style sheets, script files – all under the ‘assets’ directory in my case)

<?xml version="1.0"?>
<configuration>
  <system.webServer>
    <staticContent>
      <clientCache cacheControlCustom="private" cacheControlMode="UseMaxAge" cacheControlMaxAge="3.00:00:00" />
    </staticContent>
  </system.webServer>
</configuration>

A couple of things to note about these settings. Firstly – you can add any custom cache control header setting using the cacheControlCustom attribute (e.g. no-store, must-revalidate, private, public etc). Secondly using the http 1.1 max-age setting – you can specify days – by using the 1.00 notation for the hours portion of the hh:mm:ss timespan value. So the setting above will set the max-age value to 3 days – or 259200 seconds when you see it in the response header.



| Comments [2] | | #  
Saturday, May 02, 2009
Saturday, May 02, 2009 10:17:21 PM (GMT Daylight Time, UTC+01:00) (ASP.Net | C#)

I’d previously created a compression processor for dynamic content – using this excellent post for QValues by Dave Transom. Also just just discovered this post on creating an action filter for compression by Kazi Manzur Rashid.

So combining the two and we have:

public class CompressFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpRequestBase request = filterContext.HttpContext.Request;

        // load encodings from header
        QValueList encodings = new QValueList(request.Headers["Accept-Encoding"]);

        // get the types we can handle, can be accepted and in the defined client preference
        QValue preferred = encodings.FindPreferred("gzip", "deflate", "identity");

        // if none of the preferred values were found, but the
        // client can accept wildcard encodings, we'll default
        // to Gzip.
        if (preferred.IsEmpty && encodings.AcceptWildcard && encodings.Find("gzip").IsEmpty)

            preferred = new QValue("gzip");

        HttpResponseBase response = filterContext.HttpContext.Response;
        // handle the preferred encoding
        switch (preferred.Name)
        {
            case "gzip":
                response.AppendHeader("Content-encoding", "gzip");
                response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
                break;

            case "deflate":
                response.AppendHeader("Content-encoding", "deflate");
                response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
                break;

            case "identity":
                break;
            default:
                break;
        }
    }
}

Which when applied as below to any action method will enable compression if it’s supported by the browser.

[AcceptVerbs(HttpVerbs.Get)]
[CompressFilter]
public ContentResult SiteMap()
{
    return new ContentResult
               {
                   Content = SyndicationHelper.GetAggregateSiteMap(),
                   ContentType = "application/xml; charset=UTF-8"
               };
}

Download here: QValueAndCompressionFilter.zip



| Comments [7] | | #  
Wednesday, April 08, 2009
Wednesday, April 08, 2009 2:09:20 PM (GMT Daylight Time, UTC+01:00) (ASP.Net | Other Tech)

imap Trying to find a good ASP.Net web hosting company that represents the balance I need between cost and features turned out to be harder than I thought.

I recently signed up with Discount ASP.Net – and you know what – for just ten dollars a month – they offer excellent value for money, great support, a good in-house control panel and good bandwidth. They’ve kept their setup very simple and standard – with just one type of account (Computer security design principle number three – assurance vs. complexity).

On a single account, you have one root site – one domain and as many additional domains pointing to that ‘same’ root content as you like. You cannot point domain names to subdirectories – so you’ll need to re-write yourself if you want another domain name pointing to different content.

But then I discovered their ‘standard’ application pool recycle settings, which are: 1) You are idle for twenty minutes, 2) the worker process exceeds a 200MB working set, or 3) CPU utilization exceeds 70% for more than 3 minutes. To be honest these are all more than adequate for a single site, blog, or the standard five page company website. But dasBlog is a little memory hungry, and with two blogs and a photo gallery – my app pool was doing flip-flops many times a day.

So I searched and searched – and was generally unimpressed with the list of hosting companies ranked by some of the reviews. Sites were either unprofessional – or there was enough mixed reviews to put me off. Others were just too vague about what was in the package and others were not on W2K8/IIS7 yet (and I need IIS7).

So it came down to ORCS Web Hosting who are in a Peak 10 data centre – and Server Intellect – who are in a data centre located in the the Infomart Hotel in Dallas – now a part of DCI Technology Holdings.

Server Intellect truly impressed me with their pre-sales support and they were super keen to get the business (even if it was just me). And their Tier 2 plan allows for multiple wwwroot directories and domain names – which is sweet. However not yet having a shared plan on W2K8/IIS7, combined with a personal recommendation for ORCS Web Hosting from this guy, swung me towards ORCS Web.

ORCS Web Platinum Shared Hosting is not that different from the Discount ASP.Net setup in so far as you get a single root site and unlimited additional domain and mail aliases, but that’s where the similarities end. The app pool has no idle time limit, no CPU limit, and an 800MB working set threshold. Data transfer allowance is 100 GB/month – and the site is in a data centre with fat pipes from…

...multiple Tier-1 Internet carriers with multiple fully-lit OCx connections.

…served in part by Level 3 Communications.

So that’s where I am now… although – it was a bumpy start. ORCS appear to have built, or had built, a new control panel which was err… buggy. Support was fine though and we got the wrinkles ironed out.

So ORCS Web hosting is my new home in cyberspace for the moment; nice view, plenty of room, and fast  - will be here until the clouds come along :-)



| Comments [0] | | #  
Monday, September 29, 2008
Monday, September 29, 2008 12:42:56 PM (GMT Daylight Time, UTC+01:00) (ASP.Net)

Having been inspired by BlogSvc.Net's recent switch to the ASP.Net MVC framework - I decided that now was as good a time as any to begin to get up-to-speed with ASP.Net MVC

There's a ton of blogosphere content out there already (I've included some of the more helpful links at the bottom of this post). Access to the source at http://www.codeplex.com/aspnet  combined with a healthy dose of Reflector over the System.Web.Routing assembly...  and things are slowly starting to fall into place.

While working through the routing process; how routes are defined, the order they're evaluated and how a default route catchall can be defined... I got a bit hung-up on default routing and in particular the routing of Default.aspx.

When you create a new ASP.Net MVC project (Preview 5).. the project template creates a Default.aspx page and places the following in the code behind...

public void Page_Load(object sender, System.EventArgs e)
{
    HttpContext.Current.RewritePath(Request.ApplicationPath);
    IHttpHandler httpHandler = new MvcHttpHandler();
    httpHandler.ProcessRequest(HttpContext.Current);
}

What's happening here, is that the Url requested (which was Default.aspx) is being re-written to the application root "/" and then transferred off of the Webform HTTP handler and onto the MvcHttpHandler. A request for "/" will match the Default route entry (show further down)... eventually sending us onto one of the route controllers.

In order to really 'grok' what's going on here in just these few lines of code (and the first a lot of people are going to see when they start a new ASP.Net MVC project) ... you need to understand a couple of things. Firstly, there are several different classes named (in part at least) handler, or module in the System.Web.Routing and System.Web.Mvc namespace. It takes a little investigation to work out the responsibility of each of these classes and how they each fit into the complete pipeline, from initial request to the final response and output. For starters - the MvcHttpHandler in the Page_Load event above is not the normal entry point into the framework. (In fact, unless the handler defined for the matching route after the transfer to MvcHttpHandler, is of type MvcHandler - you'll get an exception that says so - also the reason you need to comment out the Page_Load event handling code when you are using Phil's route debugger shown below).

The normal entry point is the UrlRoutingModule  - which you'll see registered in the Web.config. It's this module that inspects incoming requests... checks them for matches against routes in the RouteCollection... and then transfers the request over to the handler that was specified in the matching route entry. One of the best conceptual descriptions of the ASP.Net MVC framework I've come across - was "...think of ASP.Net MVC as a dynamic HttpHandler selector - each route that you define specifies an HttpHandler that will deal with the request from that point on - and that's it". So routes are really nothing more than a way of allowing you to define an HttpHandler that will take care of the rest of the request/response processing for matching Urls.

Back to Default.aspx. The first question I had was why wasn't the default route capturing requests for Default.aspx and why for that matter, was the Page_Load event handling code above required in the first place?

Here's the "Default" route that is created in Global.asax for a new solution...

routes.MapRoute(
    "Default",                                              // Route name
    "{controller}/{action}/{id}",                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
);

What this route says is... take anything... any Url and treat the first segment as the name of the controller that will be used to handle the request, then take anything as the second segment and use that as the controller method that will be called (controller action) and take anything as the third segment and use that as a parameter for the action method.

Well it turns out there are a couple of reasons that a request for Default.aspx won't work with the route above.

To start .. grab a copy of Phil Haack's Route Debugger. Build the solution and then add a reference to the RouteDebug assembly to your project.

Also remove, or comment out the Page_Load event code in Default.aspx (shown above) since what we want to test here, is what's required to route Default.aspx without the use of any redirection code.

Add Phil's debugger to the Global.asax Application_Start() event handler, as below..

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    RouteTable.Routes.RouteExistingFiles = false;
    RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
}

Also note that extra line I've added... for RouteExistingFiles - which is set to false (the default). This is the first reason that Default.aspx won't be routed. The routing module will not (by default) apply routes to Urls that actually exist as files in the web site. This is a good thing... since it allows you to include static content and regular ASP.Net pages in your project. However also be warned that when it is set to RouteExistingFiles = true, all static content (like stylesheets and JavaScript) along with regular ASP.Net pages may no longer be served from the site (if you have a catch-all or default route in place). This might be the desired behavior though.. if you wanted to serve all of your static content from another web server and guarantee that no static content would be coming from your application server.

Try running an ASP.Net MVC app with the code in Application_Start() as above (be sure to have commented out the Page_Load event handling code in Default.aspx first!).

What you should see is that Default.aspx loads as normal and we've not entered the MVC framework.

Now change change RouteExistingFiles to RouteExistingFiles = true and temporarily comment out Phil's route debugger - like this...

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    RouteTable.Routes.RouteExistingFiles = true;
    //RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
}

What you should see now is a yellow screen of death (YSOD) "Server Error in '/' Application - The resource cannot be found. HTTP 404 etc..."

The reason we're seeing this is because (again - in the default ASP.Net MVC Solution) there is no controller called Default.aspx. We supplied 'something' in the url and that 'something' was matched as the first segment or token of the Default route (incidentally, naming the route 'Default' has nothing to do with 'Default.aspx' - it's just a name for the route and could be anything). The route actually matched. If you uncomment the RouteDebugger and run it again - you'll see that the Default route was in fact matched to our request. matched_01 However when the framework (the MvcRouteHandler, and then the MvcHandler controller factory in this case) tried to find a controller class called 'Default.aspx' - it couldn't, and so returned an Http 404 file not found error.

The only controllers that exist in the solution - are HomeController, and the AccountController - and our controller token didn't match either of these. You can try changing the Url to "/boo" or "/foo" "/mycontroller" (comment out the RouteDebugger again) and you'll see the same 404 not found error returned again each time. We match the default route, but there are no controllers for any of these controller tokens. However if you try "/home" - bingo... you'll hit the home controller, with a default action of Index and the Index.aspx page in the Views/Home/ directory will be served up as the response (Note: with RouteExistingFiles still set to true... the style sheet won't be served).

Ok so what if we want Default.aspx to route to the home controller? We could add the following route...

routes.MapRoute(
    "Start",                                                // Route name
    "Default.aspx",                                         // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
); 
But this must appear above the Default route since the first route that matches - wins.

With the 'Start' route in place, Default.aspx is now being matched literally - and the default values for the controller, action and id are being used to serve up Views/Home/Index.aspx (again - this will only work if RouteExistingFiles = true while a 'real' Default.aspx page still exists in the root of your site).

As an aside - if we wanted to catch all requests for non-existent controllers you could use a route like this..

routes.MapRoute(
    "Catchall",                                              // Route name
    "{*anything}",                                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
); 

The 'Catchall' route would need to come before the 'Default' route and after any controller specific routes like '/home /news /products... etc - although some might question the wisdom of having such a route. An alternative approach would be to correctly deal with the 404 file not found errors with a custom error page and then take things from there.

So from this little exercise we can now see that there are two things that prevent the routing of Default.aspx. The first - is that the default setting for RouteExistingFiles  is false - and for good reason too. The second, is that even if we were going to route Default.aspx, matching on the Default route still doesn't help since Default.aspx will be matched to the controller token in the route, and there is no such controller in the solution.

So what are the practical options for dealing with Default.aspx? Well I think there are three you can choose from...

  1. Leave Default.aspx unrouted and unredirected as the entry point to your application - with static links that take your users into the MVC portion of the app (or other static content).
  2. Redirect Default.aspx in the code behind, either using the Page_Load event handler code as shown above, or use Response.Redirect("~/home") to send them to the Home controller (although this is a round-trip redirect).
  3. Rename or delete Default.aspx. Despite the warning in the markup that says that default.aspx is required to ensure that ASP.NET MVC is activated by IIS when a user makes a "/" request... it's not actually needed in either the VS dev server, or IIS7. The default request will remain an application root request "/" and will be caught by the default route and sent to the home controller.

 

And that's that. I really don't feel good about doing much until I understand how things start... and how we got to point B from point A. The learning curve in ASP.Net MVC is fairly steep, and started (for me at least) with the humble Default.aspx page.

Here are a few links that helped along the way (and are current for ASP.Net MVC Preview release 5).

http://msmvps.com/blogs/luisabreu/archive/2008/07/04/the-routing-series.aspx
http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx
http://bradwilson.typepad.com/blog/2008/08/partial-renderi.html
http://lostintangent.com/2008/07/03/aspnet-mvc-controlleractioninvoker-part-1/



| Comments [1] | | #  
Saturday, June 14, 2008
Saturday, June 14, 2008 2:09:49 PM (GMT Daylight Time, UTC+01:00) (ASP.Net | CSS/XHTML)

I have a lot of respect for experienced CSS/XHTML designers having lived through two medium size projects that both had accessibility and XHTML Strict requirements - it was tough going. We survived the Peekaboo Bug, the Guillotine Bug, and the Duplicate Characters Bug to name a few.

I recently updated the design of my blog and my photo gallery component. Trying to keep the site IE6 compatible brought back painful memories. Thanks to this kind blogger at 456 Berea St - I managed to squash the last CSS bug in the layout - a problem I was having with space appearing between <li> items in a list that contains block level elements. I was kind of surprised to see this in IE7. The block element in this case was an <a> tag - and needed to have a width or height set in order to close up the gap between the containing <li>s; a very common layout for creating hover effects on menu items in <ul> lists. Sigh.

Lone blogger 1 - IE6/IE7 0



| Comments [0] | | #  
Saturday, May 31, 2008
Saturday, May 31, 2008 12:31:49 PM (GMT Daylight Time, UTC+01:00) (ASP.Net)

Visual Studio 2008 Professional shipped with the Visual Studio 2008 Team System unit test framework (which in Visual Studio 2005 was not available in the Professional version).

The test system is pretty cool - with excellent test code generating options and wizards. One really interesting feature is the ability to create tests for ASP.Net projects (file based, or application based). The tests created will be decorated as follows:

   1: /// <summary>
   2: ///A test for DemoTest
   3: ///</summary>
   4: [TestMethod()]
   5: [HostType("ASP.NET")]
   6: [UrlToTest("http://localhost/demo")]
   7: public void DemoTest()
   8: {
   9:     string input = "Boo";
  10:     string actual = "Boo1";
  11:     actual = input + "1";
  12:     Assert.AreEqual(input , actual);    
  13: }

 

Previously to the only way to test components in an ASP.Net application - especially one that depended on any custom configuration settings or configuration sections in the Web.config file - was to take a copy of all the required settings - and place them in an App.config file for your Unit Test project. Then using a test running framework like TestDriven.Net or NUnit - run the tests.

With the new Visual Studio 2008 test suite, you can simply use the Test Wizard to create the test, which will detect all the dependencies and create test methods that look like the one above. When run - the tests execute in the same processes as the referenced web application (and so use the same configuration settings).

If you want to debug your tests - you then have to attach the debugger to the either the development web server, or the IIS process that is hosting your local website.

Here's a link to an article that will show you how to do either - How to: Debug while Running a Test in an ASP.NET Solution.

However you have to re-attach the debugger to the process manually for every test run you want to debug, which is a drag. After googling for a while - I wasn't able to find an easy way to get F5 debugging that would automatically attach to the correct process (a command line option in the project maybe?). Still, the essence of the tests are to test assertions against your code and expected results so for a mature application test suite this isn't too big a problem. However for a TDD design approach - having to re-attach the debugger on each run is pretty inconvenient. Am I missing something?



| Comments [1] | | #  
Tuesday, May 27, 2008
Tuesday, May 27, 2008 8:31:26 AM (GMT Daylight Time, UTC+01:00) (ASP.Net | Utilities)

Wow - Google and searchers were all over the "URI templates and hackable search engine friendly URLs" line in my short WPF post below, which means there's a lot of interest in URI templates.

I found this post on Creating a Template based URI very helpful. You DON'T have to be building a WCF service to use these. You MUST however include a reference to the System.ServiceModel.Web assembly in your project before you can use the System.UriTemplate type.

Scott Hanselman's presentation at MIX08 on MVC was also very helpful as well as it clarified many things (not to mention being hugely entertaining).

The URI Templates of .Net 3.5 don't have anything to do with REST - they're just a utility class that helps to bind and match templates and parameters to and from URIs. When combined with a URL rewriter they make it easy to present hackable URLs the world.  As Scott said in his presentation - the URLs in a browser window are effectively part of the UI - and so should be treated accordingly when possible.

So... mystore.com/clothing/shirts would be cool, as opposed to mystore.com/products.aspx?type=clothing&category=shirts



| Comments [0] | | #  
Wednesday, August 01, 2007
Wednesday, August 01, 2007 7:52:59 AM (GMT Daylight Time, UTC+01:00) (ASP.Net | Enterprise)

A colleague just sent me a link to the MIX07 presentation of ASP.Net Dynamic Data Controls. I'd actually seen this demo before - but something about taking a quick second look made me want to blog about it.

Is it just me, or are there other people out there that kind of wince when they see these sort of presentations? (ignoring the obvious 'prop and holding' behavior of Mahesh Prakriya and his bottle of water).

The tools are definitely cool and I can see how these controls would be useful for knocking together a quick admin UI, or proof of concept app.  But the thing that kind of depresses me when I watch a video like this (with more powerful controls that let you build apps more quickly!) is the thought that there are 'developers' out there that will use this stuff and call themselves programmers - without knowing a whole lot about software development (principles - like searching, sorting, comparing, or how to build applications that are secure, transactional, and scale).

I sometimes wonder if we're headed back the good ol'days of VB6 where whole departments grew up without really knowing much about software development at all.

MS has an evangelical team, but frankly I'd like to see less proselytizing of how technology is going to make our software development lives so much better - and more holistic presentations that combine neat tools like this - with careful qualifiers that put the tools into the wider context of software design, quality and production issues.



| Comments [0] | | #  
Wednesday, May 09, 2007
Wednesday, May 09, 2007 10:37:51 AM (GMT Daylight Time, UTC+01:00) (ASP.Net)

aspnet_merge.exe exited with code 1 - read on...

There are a few good articles around that describe the differences between the default Web project in VS2005, and Web Application Projects (which are included with SP1 now). Having come from custom scripts using XCOPY in VS 2003 - I was comfortable with the Web Application Project model - knowing that the site would be compiled into a single DLL which along with all the required files to run the site - could just be copied/XCOPYd/FTPd into production.

I also think the loose directory structure and compilation options of the standard Web Project in VS2005 are pretty cool too. That said for a lot of Web applications - I think the Web Application Project makes a lot of sense.

Rick Strahl has written a pretty good summary of the issues and differences between the default Web Project and Web Application Project formats - at Web Application Projects and Web Deployment Projects are here

We recently switched back to a Web Application Project on one project - but wanted to keep the Web Deployment Project - since the compiler options, pre and post build command options (in the source project file) were a convenient place to perform configuration specific tasks in a build.

For one site in particular we wanted to be able to publish new binaries quickly if needed and so the Web Deployment options were set to 'Merge all outputs to a single assembly', and 'Treat as libaray component'.

I chose the default namespace for the Web Application Project as the assembly name to merge to in the Web Deployment Project.

And at that point I was stuck - the build failed with am  '"aspnet_merge.exe" exited with code 1' failure message.

I enabled Detailed MSBuild messages in the Output window - (Tools, Options, Projects and Solutions, Build and Run - bottom combo box) and saw the following message the next time I tried to build.

The target assembly 'DCL.MG.Web' lists itself as an external reference.

After some experimenting - I told the Web Deployment Project to build to an assembly with a different name - in this case "DCL.MG.Website" - and I discovered two assemblies in the output directory.  One called DCL.MG.Web.dll (from my Web Application Project) and one called DCL.MG.Website.dll which the Web Deployment Project had created.

Using IL DASM - I could see that the DCL.MG.Website assembly had only two classes in it - an ApplicationBrowserCapibilitiesFactory and global_asax. The rest of the site had compiled into the DCL.MG.Web assembly fine.

I can live with this extra assembly - it's small and uploads fine with the other binaries when we do an update. I'm just a little curious as to why this is happening - why we're not getting a single assembly, and whether anyone else has discovered this as well.



| Comments [0] | | #  
Saturday, March 24, 2007
Saturday, March 24, 2007 5:35:21 PM (GMT Standard Time, UTC+00:00) (ASP.Net)

Discovered Mike Ormond's blog after the MSDN Roadshow - which in turn led me to this definitive article on ASP.Net Viewstate from Dave Reed. Mandatory reading.



| Comments [0] | | #  
Wednesday, February 07, 2007
Wednesday, February 07, 2007 3:44:13 AM (GMT Standard Time, UTC+00:00) (ASP.Net)

Having recently moved my DasBlog tech blog from one domain to another I wanted an easy way to redirect all the traffic from the old domain, to the new one.

My shared hosting provider doesn’t provide directory level redirects (although this can be done in IIS) – only domain level.

There are also plenty of examples out there for how to redirect a single page – typically Default.aspx. What I wanted was true SEO friendly Url for Url permanent HTTP 301 redirection for every page and service in the blog.

Since the blogging engine and directory contents were the same all I needed to replace was the name portion of the domain name for the incoming Urls, redirecting them to the same resource, but on the new domain.

So I knocked to together an HTTP Handler that looks like this.

namespace DotBits.Handlers
{
   public class RedirectHandler : IHttpHandler
  
{
      public void ProcessRequest(HttpContext context)
     {
        context.Response.Status = "301 Moved Permanently";
        string url = context.Request.Url.ToString().Replace("dotbits", "58bits");
        context.Response.AddHeader("Location", url);
      }

      public bool IsReusable
     {
        get { return false; }
     } 
   }
}

And then a reference to the handler with the correct path statement in the Web.config file.

<system.web>
   <
httpHandlers>
     <
add verb="*" path="blog/*.*" type="DotBits.Handlers.RedirectHandler"/>
   </
httpHandlers>
</system.web
>

Presto. Works for all registered ASP.Net resource types; .aspx, .asmx, .axd etc.



| Comments [0] | | #  
Thursday, November 23, 2006
Thursday, November 23, 2006 1:32:07 AM (GMT Standard Time, UTC+00:00) (ASP.Net)
As site I’ve been working on recently required that all pages be XHTML Transitional compliant, with one exception. A special document page of the site needed to be in XHTML Strict format. 
 
The problem was how to get ASP.Net 2.0 to render all the pages in the site as Transitional, while rendering the special page as Strict.
 
Setting the xhtmlConformance option as bellow in the system.web section - will render all controls and pages as Transitional.

<system.web>
   <
xhtmlConformance mode="Transitional"/>
</system.web>

Since the main pages of the site are in the same root level directory - defining another web.config file for just the document.aspx wouldn't work.

Instead I used the location element (outside the <system.web> section). This will let you override any of the <system.web> settings for a specific location, directory or individual page.

<system.web>
   <xhtmlConformance mode="Transitional"/>
</system.web>

<location path="specialpage.aspx">
  <
system.web>
   <
xhtmlConformance mode="Strict"/>
 </
system.web>
</
location>

The hidden viewstate input element for the special page is now correctly wrapped in a <div> and the form name element no longer appears (just the ID).  The special page now passes for Strict while the rest of the site will pass for Transitional.

One last sneaky trick that took me a while to uncover - which could almost be consider a bug in ASP.Net - is that by default, with the default browser definition list - ASP.Net 2.0 doesn't recognise the W3C Validator's user agent - and no matter what you do - will not render Strict pages correctly and therefore all validation attempts for Strict will fail.

The trick is here....

http://idunno.org/displayBlog.aspx/2005080101

I created an App_Browsers folder in the project - and placed the browser definition for the W3C validator from the site above in this folder. The W3C Validator service was then receiving correctly rendered pages and passing the special page as strict.

:-)



| Comments [0] | | #  

search

categories

on this page

ads

archive

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