Thursday 1 October 2015

What to pack for a 2-5 day cycle tour

Packing light but right for a 2-5 day cycle tour is never easy, too much and you have a miserable time on the hills, but not enough and you'll be spending unenjoyable evenings in the day's wet and sweaty lycra. 

Over many short tours in both the UK and abroad this is the list I've settled on. It should all fit in a decent sized single pannier whilst providing enough comfort for an enjoyable evening in the pub. If packed well it leaves plenty of space for a full day's food and an extra water bottle in hot weather. 


Cycling kit 

  • Shorts
  • Pants
  • Base layer
  • Jersey
  • Jumper/fleece
  • Waterproof jacket (cycling specific or lightweight walking ones are best)
  • Socks
  • Shoes
  • Cycle gloves
  • Vaseline/cyclists arse cream (optional but helps prevent chafing)
Plus for hot weather: 
  • Sunglasses 
  • Sunscreen 
And for cold/wet weather: 
  • Leg warmers / long legged shorts
  • Overshoes 
  • Long fingered warm/waterproof gloves (depends how cold you get) 
  • Waterproof trousers (optional, I don't usually bother) 
  • Snood/neck warmer (I find the Buff style ones most useful and versatile)


Equipment 

  • 2x Water bottles 
  • Pannier rack
  • Pannier (I prefer single to prevent overpacking but double is also okay)
  • Lights (if heading at all offroad a headtorch often works well as a front light)
  • Multitool (at least 1 with chain tool between group)
  • Few cable ties (for emergency repairs)
  • Pump (one per person is best, should be capable of the required pressure)
  • 2x Spare inner tubes 
  • Puncture repair kit 
  • Tyre levers 
  • Bike oil (one between group is fine) 
  • Emergency sweets/energy food e.g. small bag of sweets, flapjack
  • Spare brake pads/blocks (if likely to be needed)
  • Lightweight lock (optional if staying somewhere with secure storage. I use a £3 one from a bargain store just to prevent walk-offs if I take one at all) 


Useful repair tools

  • Emergency chain link or chain pins to match your chain (for use with chain tool)
  • Small adjustable spanner (one between group)
  • Small pliers (one between group)
  • Spare brake and gear inner cable (one between group)
  • Spoke key (one between group) 
  • Few misc. bike nuts n bolts 


Overnight kit

  • 1x set of lightweight evening clothes minus jumper and waterproof which can be shared with cycling kit 
  • Pants (quantity according to your preferred hygiene level) 
  • Socks (as above) 
  • Lightweight shoes 
  • Toiletries (small are best if not provided)
  • Earplugs (to combat those dorm snorers) 
  • Travel towel (if not provided, they usually are)

Monday 2 December 2013

Pocket lint : The unlikely culprit of iPhone 5 charging problems

Okay, so the title says it all. Your lovely iPhone 5 won't charge correctly and it's driving you mad. Your once worshipped aluminium deity has become the devil incarnate for refusing to accept your beloved lightning connector. Initial engagement with said connector goes well but bottoms out awkwardly a few millimetres from securely connecting to the object of your affections, teasing you with the faint glimmer of a prolonged charge only to cruelly renege when bumped, knocked or otherwise disturbed.

The answer of course is quite simple; the more dangerous than a daddy-long-legs pocket lint! Take a deep breath, it's curable.

Finding a pin, paperclip or other thin'n'pointy simply poke the small end into the dock on your phone, let it hit the bottom and scrape hard. If you've done it right the bedrock of compressed pocket fluff will crumble before your very eyes and a great river of linty fragments, the likes of which put a new clothing day belly-button clean to shame will flow forth.

Now celebrate! It wasn't broken, just fluffed up. Eternal gadgetary happiness is once again within reach.

Wednesday 8 May 2013

London to Paris Cycle - Day One - London to Portsmouth

"A long day with some notable climbs through beautiful Surrey countryside"
Distance
77.4 miles (11h 30m)
Ascent
1459m
Profile
Download route (coming soon)
GPX

This is a tough cycle through some beautiful Surrey countryside with a few bonus hills thrown in for good measure. Leaving from Surbiton there's little trouble with traffic so an escape can be made down the aptly named Portsmouth Road before leaving for a less direct route around Cobham.

From Cobham you head south skirting round Guildford on the east and taking in the North Downs and Surrey Hills. The scenery is fantastic and the few small hills keep things lively. Dunsfold at ~35 miles proves itself to be a welcome and worthy lunch stop with simple home cooked food at The Sun Inn and a village shop for basic amenities.

With a few hills under your belt, a belly full of pie and nearly half the distance covered you could be feeling proud of yourself leaving The Sun Inn. However, keep something back as the second half of this route is characterised by unrelenting undulations, kickstarted by the formidable Bexley Hill which at 17% is no laughing matter with panniers, a race compact crank and two pounds of beef and gravy in your stomach.

Bexley over the route saunters pleasantly to the coast along small roads with a pleasant climb up Hyden Hill shortly after passing under the A3 marking the home straits. The route winds down into Portsmouth over many more miles than tired legs would prefer; a late adrenaline surge being provided by the Portsmouth traffic and crossing of four lane carriageways to reach your final destination, Portsmouth Travelodge.

Portsmouth Travelodge is basic but well provisioned with a Wetherspoon's style eatery downstairs serving 'til 10pm. Bikes can be stored in your room but prepare for some bemused looks as you sweat your lycra-clad way up the stairs against a tide of shiny shoed revellers.

Other stages

  • London to Paris Cycle Route
  • Day 1 - London to Portsmouth
  • Coming soon - Day 2 - Portsmouth to Caen by ferry, then Caen to Villerville
  • Coming soon - Day 3 - Villerville to Les Anderlys
  • Coming soon - Day 4 - Les Anderlys to Paris
  • Coming soon - Day 5 - Paris to London by Eurostar

London to Paris Cycle Route

My London to Paris 5 day route is on predominantly minor roads through some beautiful areas of Southern England and Northern France. Taking in the Surrey countryside it hugs the pretty North Normandy coastline before heading inland to cross the Seine and follow it to Paris.

No trip would be complete without a cycle round Arc de Triomphe and visit to the Eiffel Tower before finishing Tour de France style up the Champs Elysees.

It was the coldest spring on record. We stared through the kitchen window at the thick blanket of snow that had engulfed the garden for the past week.

"It won't last" I said, and booked the Paris to London Eurostar tickets for Easter Monday.

It was the coldest easter on record too.

The route is designed for a morning daytime crossing from Portsmouth to Caen (Ouistreham) but would work equally well taking the overnight ferry which avoids the expense of the Travelodge and grants you a relaxing second day to explore the pretty villages along the Normandy coast. Just leave plenty of time to get to the ferry!

Other stages

  • London to Paris Cycle Route
  • Day 1 - London to Portsmouth
  • Coming soon - Day 2 - Portsmouth to Caen by ferry, then Caen to Villerville
  • Coming soon - Day 3 - Villerville to Les Anderlys
  • Coming soon - Day 4 - Les Anderlys to Paris
  • Coming soon - Day 5 - Paris to London by Eurostar

Wednesday 5 January 2011

Development Ideas For Small Companies & Teams

Having worked predominantly in small companies and teams I've collected a few wisdoms along the way.

General

  1. Address your biggest constraint, it's the only one that matters. Repeat
  2. Solve only problems you actually have
  3. Mandate quality appropriate to the software at hand

Communication

  1. Take time to understand stakeholders' expectations and availability
  2. Establish a regular cadence for stakeholder reviews and other events
  3. Frequently demonstrate new functionality, even if incomplete. Limit the potential for misunderstanding
  4. Question things you don't understand, however small
  5. Avoid email for in-depth discussions. If you must try using stories, tables and mockups to communicate requirements then talk it through over the phone
  6. Adopt industry standard terminology for your domain

Process

  1. Reduce process overhead. How often do you use the audit trail you might think you need?
  2. Prioritise work with a simple familiar system such as a queue. Consider limiting this to a small number of next features. Get enough detail to roughly size the features then analyse them in detail as Just-In-Time as is tolerable
  3. Adjust your process to suit you

Planning

  1. Don't start until you understand what "finished" means
  2. Plan, but don't expect to have thought of everything
  3. Timebox unpredictable tasks and have a fallback plan
  4. Work on as-few-a concurrent projects as possible
  5. Understand your development capacity and be honest about what you can achieve

What advice would you have for someone in a small company or team?

Why be "agile" when effectiveness is what counts?

It struck me today whilst talking to a colleague how the term "Agile" can easily be misunderstood.

Colleague: "Agile is SCRUM, a load of stakeholders get together in a meeting and decide by committee what development will happen the next week"

"Agile" is not just SCRUM or really any other "Agile" methodology; it seems to me more of a loose collection of principles to organise delivery sensibly and allow large companies to operate more like small ones. Agile, SCRUM, Kanban, XP are just methodologies are just tools, and as any DIY enthusiast knows choosing the appropriate tool for the job is paramount in any life or death shelf hanging scenario.

"Agile", in my opinion, has become over-used and under-understood owing mainly to the aspirational nature of the word. We've seen numerous CVs with a heavy sprinkling of "Agile" dust only to find the candidate has little or no understanding of "Agile" development practices. Worth noting it's also a very easy word for agents to substitute in before others such as project, team, architecture. Agile Architecture?!

Effectiveness over "Agile"-ness

The real goal is not to be "Agile", or adopt some set of sacred practices in the hope that this new religion will suffer none of the weaknesses of the old; but discover a software development methodology that works for your team's situation. A good place to start is by learning the principles of Agile methodologies (see The Agile Manifesto and The Twelve Principles), reading a few different books, sharing experiences with people doing it in anger and not just writers or non-practising conference circuit theorists.

If you can understand these principles, and have a broad knowledge of the tools available then you're in a place when you can start to effect change.

Friday 12 November 2010

Using a Guid as a version column in NHibernate

One of the great features of NHibernate is the almost infinite control it gives you over mapping to existing database structures with its custom type mapping. Turns out custom types can also be applied to version columns for managing concurrency. Thanks to Justice for the pointer on my stackoverflow post :)

The basic method is to simply implement a NHibernate.UserTypes.IUserVersionType and implement the Seed and Next methods to return Guids. The rest is just Guid type mapping.

The custom type can then be registered in FluentNHibernate using this code:

   1:  public AnimalMap()
   2:  {                       
   3:      Id(x => x.Id);    
   4:      Map(x => x.SpeciesId);
   5:      Version(x => x.ConcurrencyId).CustomType(typeof(GuidVersionType));
   6:      Map(x => x.Name);    
   7:      Map(x => x.NumberOfLegs); 
   8:  }

The full code is below:

   1:  class GuidVersionType : NHibernate.UserTypes.IUserVersionType
   2:  {
   3:      public SqlType[] SqlTypes
   4:      {
   5:          get
   6:          {                
   7:              var types = new SqlType[1];
   8:              types[0] = new SqlType(DbType.Guid);
   9:              return types;
  10:          }
  11:      }
  12:   
  13:      public System.Type ReturnedType
  14:      {
  15:          get { return typeof(Guid); }
  16:      }
  17:   
  18:      public new bool Equals(object x, object y)
  19:      {
  20:          if (x == null)
  21:          {
  22:              return false;
  23:          }
  24:   
  25:          return x.Equals(y);
  26:      }
  27:   
  28:      public int GetHashCode(object x)
  29:      {
  30:          return x.GetHashCode();
  31:      }
  32:   
  33:      public object NullSafeGet(IDataReader rs, string[] names, object owner)
  34:      {
  35:          var value = NHibernateUtil.Guid.NullSafeGet(rs, names[0]); 
  36:          if (value == null)
  37:              return null;
  38:   
  39:          return (Guid) value;
  40:      }
  41:   
  42:      public void NullSafeSet(IDbCommand cmd, object value, int index)
  43:      {
  44:          if (value == null)
  45:          {
  46:              NHibernateUtil.Guid.NullSafeSet(cmd, null, index);
  47:              return;
  48:          }           
  49:          NHibernateUtil.Guid.NullSafeSet(cmd, value, index);
  50:      }
  51:   
  52:      public object DeepCopy(object value)
  53:      {
  54:          if (value == null) return null;
  55:          var copy = ((Guid) value);
  56:          return copy;
  57:      }
  58:   
  59:      public bool IsMutable
  60:      {
  61:          get { return false; }
  62:      }
  63:   
  64:      public object Replace(object original, object target, object owner)
  65:      {
  66:          return original;
  67:      }
  68:   
  69:      public object Assemble(object cached, object owner)
  70:      {
  71:          return cached;
  72:      }
  73:   
  74:      public object Disassemble(object value)
  75:      {
  76:          return value;
  77:      }
  78:   
  79:      public int Compare(object x, object y)
  80:      {
  81:          return ((Guid) x).CompareTo((Guid) y);
  82:      }
  83:   
  84:      public object Seed(ISessionImplementor session)
  85:      {
  86:          return Guid.NewGuid();
  87:      }
  88:   
  89:      public object Next(object current, ISessionImplementor session)
  90:      {
  91:          return Guid.NewGuid();
  92:      }
  93:  }

This works in FluentNHibernate 1.1.

Wednesday 20 October 2010

Setting the Hudson/Jenkins next build number programatically

Programatically setting the next build number in Hudson/Jenkins is a useful thing to do if you have a build pipeline. After a bit of research it seems there are 3 main approaches that could be useful in different situations:

  1. Set the nextBuildNumber.txt in your Hudson job's directory
  2. Use environment variables for build number passthrough (see @ruby_gem's post here)
  3. Use the nextBuildNumber Hudson plugin and HTTP POST the build number to it

In this post I'll be looking at option 3 - using the nextBuildNumber Hudson plugin.

Using the nextBuildNumber Hudson plugin

Firstly install the nextBuildNumber plugin. You can now set the next build number for a job manually from the plugin link that appear on the left.
To use this programatically you simulate the manual for submission with a web client. I tested it with FireFox ReST client.
To set the next build number to 999 simply formulate an HTTP POST to your Hudson URL:

e.g. http://myhudsonserver:8080/job/myhudsonjobname/nextbuildnumber/submit
With the form data
nextBuildNumber=999
And the HTTP Content-Type request header
Content-Type: application/x-www-form-urlencoded

If your Hudson is secured you'll need to authenticate your web client too.

That's it! Finally it's worth noting that build numbers in Hudson can only go up!

Thursday 16 September 2010

Error Handling CI Deployments Using Powershell

PowerShell's a great tool for use in remote deployments from a CI server. However, it's default error handling does not cause a failure on error which can leave failed deployments reporting success!

To make PowerShell scripts fail on non-terminating errors add the following line to the top of the script after any params:

$ErrorActionPreference = "Stop"
This will cause the script to fail on terminating and non-terminating errors hence preventing the Powershell script succeeding when elements of it have failed.

Thursday 26 August 2010

Better TDD By Writing Your Asserts First

Writing unit tests with an Arrange, Act, Assert structure definitely helps but the order often seems at odds with the mental process of TDD. Best practice is to write your asserts first. Here's why:

Arrange First?

Arrange-ing first is virtually impossible. You have no idea what to Arrange until you've written the Act or Assert. This is particularly important when Mocking/Stubbing as the Arrange requires knowledge of the implementation which is exactly what you don't have at this point! * see below

Act First?

Act-ing first seems more reasonable but requires something to first be arranged which can lead you down the dark path of arranging first. If there's any uncertainty about the purpose of the test, which there often is, then this can result in wasted effort.

Assert First

Assert-ing first forces you to clarify the purpose of your test early. Have you ever had that paired conversation of "What are we really trying to do here?", well asserting first is having that conversation. Sure you may not be able to write the entire assert standalone but it will help you to drive the rest of the test and implementation with a clearer mind.

* but I do have knowledge of the implementation

Really? For a trivial implementation great! But if you have an existing external dependency then how do you know that this implementation should call it directly? You don't! Sure it will eventually get called but that may not be the concern of the class under test. Switch back to top down design and let the Assert drive out the interface you require the external dependency to have. Then, and only then, either wrap the dependency to make it expose the interface you require or take the well considered decision to use it directly.

Assert first is really helpful for all but the most trivial tests and can help you achieve that holy grail of loosely coupled, highly cohesive code. Try and see if it works for you!

Breaking The Legacy Bind : A Tale Of Build Automation

I've recently moved teams and following a brief honeymoon on a greenfield web app we're now swimming in the murky waters of the "Legacy Codebase". Imagine a world with no continuous integration, patchy test coverage, untamed dependencies, COM+, a long manual build and deployment process and to top it all off, Visual Source Safe.
If this doesn't make you shudder then best stop reading now :)

Granted, there are probably many worse legacies out there and much of our underlying code is good but it's still a significant departure from the instant builds and frequent releases we had been making.

Science Friction

The initial obstacle faced here is friction in the development process. Friction resulting mainly from the repetitive manual build process. The manual build takes time; lengthening the feedback cycle. It's also tedious and error prone. These two facts alone can prevent even the most diligent of developers from making the refactorings necessary to keep the codebase in good health. Our first job - automate.

Automation, Automation, Automation

Automating the build process to run on check-in removes the tedium of doing it manually and is relatively easy to do with any CI server. Fortunately our code already has a well organised suite of NAnt scripts so we simply configured Hudson, our CI server, to run them and display the test output. A relatively quick win.

If you're not so lucky you could start with automating one part at a time. Just note down your manual process and automate each step in turn.

Migration From VSS To TFS

Using Hudson mandated the source code be migrated from Visual Source Safe to a system more suitable for use with a CI server - in our case Team Foundation Server. The migration itself is fairly straightforward with the Microsoft migration tool and instructions but can be time consuming so plan carefully, especially if your VSS repository is in constant use.

I've previously done this from VSS to SVN and there are probably tools out there to move between most source control systems, but beware, not all features are supported by all systems e.g. VSS pinning, SVN externals. Of course, if you're not concerned about the history, tags etc. you can always just check-in a fresh copy from your old source control to the new one and get going straight away.

Verify The Build

The first build is by far the biggest risk and taking the time to verify it is very worthwhile if you care about the quality of your software. This cost us a couple of days due to build/env issues but it's either that or the entire thing is a no-go so don't be tempted to scrimp here!

Comparison tools such as WinMerge and BeyondCompare can help here to compare the checkouts from each source tree or even build artefacts.

Building For The Future

We're not done here! An automated build is a huge leap forward and allows you to concentrate on skilled activities that deliver more value but it's by no means the end.

Build time is important and reducing it is next on our hitlist. Time spent waiting for a build is often time wasted or forces you into the undesirable world of multitasking. If your build takes longer than it does for you to make a cup of tea then there may well be room for improvement.

Final Thoughts

Dealing with unfamiliar or hard to work with code can be daunting. In my (limited) experience it's all about confidence - get stuck in, and little by little you'll find a path forwards. If you're struggling then I'd highly recommend Michael Feathers' Working Effectively With Legacy Code (ISBN 0131177052) for some inspiration.

Friday 9 July 2010

Barcamp Blackpool 2010

There's been a US and UK election since my last post but it's time to saddle up and ride this ol' blog horse once more. Besides, this event really does deserve a mention.

Q: What do you get when you take several hundred sticks of rock, a wizard, 120 technology people, a pieman, a sprinkling of speakers, a free bar and leave them all in a casino ballroom for a day?

A: Barcamp Blackpool (@bcblackpool)

For anyone who's not attended such an event before it's a free developer and technology unconference.

Unconference!? What's that?

Talks are arranged on the day, simply turn up with one and stick a post-it with your talk name in one of the available slots. Delightfully low tech and the ensuing speaker scrumdown is a marvellous spectacle. Only the strongest survive. If Attenborough made people documentaries ...

Once talks are posted you're then free, after a quick ice-breaker, to browse the line-up and drop in on anything that takes your fancy.

Talk, Talk, Talk

Talks covered a variety of subjects both technical and non-technical subjects from British Sign Language to jQuery to Ferret Keeping although it's fair to say the majority of talks have an IT bias.

Excepting the free pie for lunch notable highlights included sessions on 45 Second Elevator Pitches, IT Recruitment (by a recruiter and nice chap I should say), Sociograms (that's connections between groups to you and me), IT Education and more.

Educational part over it's on to evening entertainment including free food, free bar and magic show before a careless plunge into the murky depths of Blackpool proper. Did I mention there's some free stuff?

A special mention is also deserving of the organiser, and my pink haired ferret-loving @esendex colleague Gemma Cameron (@ruby_gem) whose tireless enthusiasm, merry band of helpers and fondness of wine made the event hugely entertaining.!

The Rollercoaster Of Barcamp Love

  • The Big One @ Blackpool Pleasure Beach
  • Inspirational Speakers
  • My first, and I suspect last ever, game of Werewolf*
  • Karaoke Fun Bar - Blackpool's 8-Mile styled rap dungeon complete with MCs
  • Pies
  • Squirty Gertie
  • Exchanging Ideas

* When Barcamp became Band-Camp. Ask @neilkilbride for his cup truly overfloweth.

Barcamp comes to Nottingham

Celebrate! Barcamp is coming to Nottingham. Join the discussion at the Google Group here.

Wednesday 8 July 2009

ASP.NET MVC Security Trimming

We've been searching for a good way to security trim our links and button since the MVC Beta. After raising an issue on CodePlex MS assured us this would be made easier in the RTM. Turns out they were right.

Our security trimming requirements were to:

  • Use the existing ASP.Net MVC role based authorization offered by the Authorize attribute
  • Support the trimming of Action Links, Action Urls and custom scenarios

We wanted to be able to give Administrators full access to pages like this:

Untrimmed page - this user is an administrator so all links are visible

Whilst also giving standard users access only to the actions they are allowed to perform as shown below:

Trimmed page - this user has fewer privileges so the links adapt accordingly 

How Does It Work?

ASP.Net MVC 1.0 introduced the ReflectedControllerDescriptor and ReflectedActionDescriptor classes which can be used to identify the target controller and action method for a given action. The code below shows how a HasActionPermission method can be implemented to decide whether a user is authorized to perform a particular action according to the use of the Authorize attribute at the controller or action level. From this method it's fairly trivial to implement a raft of security trimmed extension methods such as SecureActionLink to complement their non-trimmed counterparts. We use this for Sharepoint style pop-up command lists and it works very nicely.

Example Code

public static bool HasActionPermission (this UrlHelper helper,
 string actionName,
 string controllerName)
{            
  // Get the controller
  var controller = GetController(helper, controllerName);
  var controllerContext = new ControllerContext (helper.RequestContext,
                                                 (ControllerBase)controller);        

  var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());            
  var actionDescriptor = controllerDescriptor.FindAction (controllerContext,
                                                          actionName);

  // action does not exist so in this example
  // we'll authorise the request
  if (actionDescriptor == null) return true; 

  var authContext = new AuthorizationContext(controllerContext);

  // run each auth filter until one fails
  // performance could be improved by some caching
  foreach (IAuthorizationFilter authFilter 
           in actionDescriptor.GetFilters().AuthorizationFilters)
  {
    authFilter.OnAuthorization(authContext);

    if (authContext.Result != null) 
      return false;
  }

  return true;                       
}

private static IController GetController(UrlHelper helper,
                                         string controllerName)
{
  // Get the controller type
  controllerName = controllerName ?? 
                   helper.RequestContext.RouteData.GetRequiredString("controller");

  // Instantiate the controller and call Execute
  var factory = ControllerBuilder.Current.GetControllerFactory();
  var controller = factory.CreateController(helper.RequestContext,
                                            controllerName);

  if (controller != null) return controller;

  throw new InvalidOperationException(
    String.Format(
      CultureInfo.CurrentUICulture,
      "Controller factory {0} controller {1} returned null",
      factory.GetType(),
      controllerName));
}

Tuesday 1 July 2008

Practical ASP.NET Url Rewriting with UrlRewriting.Net

This post describes briefly how I implemented url rewriting with the UrlRewriting.Net package.

My objectives were simple, to:

  1. Use simple readable urls wherever possible e.g. http://domain.com/app
  2. Honour querystring parameters passed to pages e.g. http://domain.com/app?param=value
  3. Map several Urls to one implementation page e.g. http://domain.com/red => http://domain.com/colours.aspx?name=red
  4. Not have to create physical folders for all virtual folders e.g. /red does not actually exist on disk!
  5. Ensure all ASP.Net functionality remains intact e.g. postbacks, themes, authentication, AJAX
  6. Must work for apps rooted at the domain e.g. http://domain.com/ and in subdirectories e.g. http://localhost/devapp/.

This was based on the following setup:

  • An IIS 5.5/6 web server
  • IIS wilcard (.*) request mapping
  • UrlRewriting.Net package
  • FormRewriter control adapter (see ScottGu's article here)
  • An existing large site with default pages named default.aspx

I chose UrlRewriting.Net as it seemed fullest featured package out there, and the only one I tested that would work correctly with FormsAuthentication and ASP.Net AJAX.

With this running 'out of the box' I found a couple of problems.

Issue 1: Trailing slashes

This can be an issue with themes and is also required to get default pages working sensibly. The idea is to remove the trailing slash on urls except those on the authority.

For example, we want:

  • http://domain.com/ to map to http://domain.com/ i.e. no change
  • but http://domain.com/dir/ to map to http://domain.com/dir i.e. slash removed

My solution is based on the one by Fabrice here but is modified to exclude the authority part of the Url (e.g. http://domain.com/) so we don't end up in an endless loop of redirects. This rule has to be a redirect since UrlRewriting.Net will not process multiple rewrites. In any case you don't want users or search engines seeing http://domain.com/dir/ as different from http://domain.com/dir so a permanent redirect to the slashless url makes good sense here, giving a single identity to multiply addressable resources.

The rule looks like this:

<add name="RemoveTrailingSlash"
virtualUrl="^~/(.*)/(\?.*)?$"
destinationUrl="~/$1$2"
rewriteUrlParameter="ExcludeFromClientQueryString"
redirectMode="Permanent"
redirect="Application"
ignoreCase="true" />

Issue 2: The Default Page

The default page is set as an attribute in the UrlRewriting configuration and is a workaround for the default page being lost when the IIS wildcard mapping is added. However, UrlRewriting default pages do not work as in IIS - the default page is actually appended to all directory requests.

For example: http://mydomain.com becomes http://mydomain.com/default.aspx

In projects where branding and SEO are primary considerations this is unlikely to be acceptable.

Switching off the default page functionality (by removing the defaultPage configuration attribute) prevents this happening but then we have to add rules for every single instance in which we would like http://mydomain.com to map to the file http://mydomain.com/default.aspx .

The workaround here is to add the following rule last in the rewrite configuration:

<add name="Default"
virtualUrl="^([a-zA-Z0-9_\-/]*)(\?.*)?$"
rewriteUrlParameter="ExcludeFromClientQueryString"
destinationUrl="$1/Default.aspx$2"
ignoreCase="true"/>

This has the effect of re-instating default page functionality. Note the expression [a-zA-Z0-9_\-/] does not include the period (.) character (i.e. to skip mapping requests for actual pages, style sheets or other resources which usually contain a period) but should include any other characters that may appear in your url. This rule is designed to only map requests such as http://domain.com/test to http://domain.com/test/default.aspx and relies on the prior redirect to remove the trailing slash (except at the authority level).

NB 1. This rule is by no means exhaustive. Modify it to suit the naming structure of your site.

NB 2. This rule relies on the trailing slash rule being present first, otherwise it would map requests such as /test/ to /test//default.aspx .

Summary

In summary I have two core rules, the trailing slash rule and the default rule. I also have a number of site specific rules in the middle to map top level directories to a single template implementation page. My config rules looks something like this:

<add name="RemoveTrailingSlash"
virtualUrl="^~/(.*)/(\?.*)?$"
destinationUrl="~/$1$2"
rewriteUrlParameter="ExcludeFromClientQueryString"
redirectMode="Permanent"
redirect="Application"
ignoreCase="true" />

<!-- Begin app specific rules -->

<!-- Category mapping rule example - does not map querystring -->

<add destinationurl="~/Categories/CategoryHome.aspx?cn=$1"
ignorecase="true" name="CategoryHome" rewriteurlparameter="ExcludeFromClientQueryString"
virtualurl="^~/(Travel|Entertainment|Family|Health|Lifestyle|Care|Food|Fashion|Home)(\?.*)?$">
</add>

<!-- End app specific rules -->

<add name="Default"
virtualUrl="^([a-zA-Z0-9_\-/]*)(\?.*)?$"
rewriteUrlParameter="ExcludeFromClientQueryString"
destinationUrl="$1/Default.aspx$2"
ignoreCase="true"/>

Conclusion

Whilst UrlRewriting.Net has it's limitations it can be used to create a maintainable url rewritten site relatively quickly and cleanly .

Disclaimer: Incorrect use of Url rewriting can have catastrophic consequences for your site. One miswritten rule can bring your whole site down. Use with caution! :)

Wednesday 8 August 2007

Reading the CrystalReportViewer report connection string from the Web.config in Crystal Reports

Again, another common requirement when working with Crystal Reports. So just where is the official documentation? ...

When using the CrystalReportViewer control it is possible to override the report's default database connection information with your own. To do this with SQL Server is well catered for, but what about us poor developers lumbered with ODBC? Perhaps we want to store and deal with connection strings from the web.config too!

Thankfully it is possible to get Crystal Reports to use a custom connection string for ODBC as this code snippet shows:

// Get document
ReportDocument doc = this.CrystalReportSource1.ReportDocument;

// Set connection string from config in existing LogonProperties
doc.DataSourceConnections[0].LogonProperties.Set("Connection String",
ConfigurationManager.AppSettings["connectionString"]);

// Add existing properties to a new collection
NameValuePairs2 logonProps = new NameValuePairs2();
logonProps.AddRange(doc.DataSourceConnections[0].LogonProperties);

// Set our new collection to be the defaults
// This causes Crystal Reports to actually use our changed properties
doc.DataSourceConnections[0].SetLogonProperties(logonProps);

How Does It Work?

The key section is the call to SetLogonProperties which causes Crystal Reports to use the new connection properties. Despite being modifiable the existing LogonProperties of a DataSourceConnection are actually read only.

Customising the CrystalReportViewer export button default filename

Crystal Reports is generally pretty useful, it's a shame some of the more common development tasks have not been exemplified by the creators. It's easy to think many things simply aren't possible, but with a bit of research you soon find most things are.

Setting the default filename used by the CrystalReportViewer web control is one of those things. The filename used defaults to the ID property of the viewer control, so to change it simply change the ID. You can do this declaratively:

<CR:CrystalReportViewer ID="MyReportName" .... />
or programattically:
this.CrystalReportViewer1.ID = "MyReportName";
Otherwise you're into manually coding export functions etc.

Saturday 4 August 2007

ASP.Net Membership: How To Log A User In By Email Address

Great as ASP.Net 2.0 Membership and Login Controls are sometimes they fall short in functionality. A common requirement is to allow users to log-in by email address. No problem with this snippet:
void LogUserIn (string email, string password)
{
   string username = Membership.GetUserNameByEmail(email);
   if (username != null
       && Membership.ValidateUser(username, password))
   {      
       // successfully authenticated
       FormsAuthentication.RedirectFromLoginPage(username, false);
   }
   else
   {
       // Display login error
   }
}
Remember - this only makes sense if you have set the MembershipProvider's RequiresUniqueEmail property to true. Otherwise Membership.GetUserNameByEmail will simply return the first username for the email address.

Friday 3 August 2007

Making asp:Panel DefaultButton work for a LinkButton in FireFox

The DefaultButton property of an asp:Panel is great, until you want to use a LinkButton or ImageButton. It just doesn't work in FireFox. This is because FireFox does not have a click method on it's HTML anchors. Fortunately a solution is at hand. Using this JavaScript you can add a click method to anchors that are missing one i.e. those in FireFox.
function AddClickMethod (clientId)
{   
      var anchor = document.getElementById (clientId);
      // define a click function for firefox
      if (typeof(anchor.click) == "undefined")
      {
           anchor.click = function ()
           {
               eval(this.href.replace("javascript:", ""));
           }
      }
}
You can then link the function to your default link button using:
AddClickMethod ("<%= myLinkButton.ClientID %>");
... and presto, a working asp:Panel default button!

Notes

Caveat: This does not work if you have validation controls with client script enabled. You can disable client script on a control with enableClientScript=false. I haven't tried it but I imagine it works with asp:ImageButton too.