Planet Claire

some say she's from Mars

Umbraco experiment - style related constants in XSLT

by Claire 10/30/2010 2:43:00 PM

The Problem - constants in XSLT files

I often find myself using constants in xslt, especially when using the fabulous ImageGen package. But as these constants are often directly related to the theme of the website, which is controlled by the css and encapsulated in a /style or /css directory in my project, it feels wrong to hard code these constants in xslt files.  In fact I have already been bitten on the bum on one site I look after, when a design makeover caused these constants to change, and not all of the values were contained in the stylesheet - messy.

Example - Feature boxes on home page

  • The site I'm working on at the moment has a 2 x 3 grid of feature photos with captions, which link to internal pages.
  • Each photo has a specific width which is tightly tied to the design of the website and will change if the design of the website changes
  • Usually, this would be controlled in the .css file, but
  • The site editor wants to be able to switch photos & captions at will, therefore we let them by making appropriate doctypes in umbraco
  • The site editor doesn't want to know about resizing images before uploading, therefore we use ImageGen
  • In the xslt which renders the feature pics, ImageGen lets us specify a width
  • I want that width to be stored in a central place inside the /style directory, for easier maintenance

Experimental Solution

I've got an idea of how to get around my problem, but as there are always so many ways to do things, I'm not sure if it's the best, so I'm going to run with it for a while & see how we go.

XSLT extensions seem to be the standard way to access C# code from XSLT.  One thing I discovered through experimentation is that your XSLT extension library doesn't have to be a separate class library to work, you can just give the namespace of the class inside your website project.  I guess that if XSLT extensions are meant to represent re-usable libraries then it makes sense for them to be a separate class library, but in this case I want to keep my class file of constants inside the /style folder (for maintainability).

1. Add a class to the styles directory containing your style related constants

Here's my class

namespace SCB.css
public class StyleConstants

public const int HomePageFeatureWidth = 208;

public int GetHomePageFeatureWidth()
return HomePageFeatureWidth;

2. Register the XSLT extension

In umbraco's /config/xsltExtensions.config file, add this

<ext assembly="SCB" type="SCB.css.StyleConstants" alias="styleConst" />

3. Use the constant in your XSLT

Here's my XSLT file that renders the feature pics, automatically sizing the photos my editor has uploaded to the correct width

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY nbsp "&#x00A0;">
xmlns:umbraco.library="urn:umbraco.library" xmlns:styleConst="urn:styleConst"
exclude-result-prefixes="msxml umbraco.library styleConst">

<xsl:output method="xml" omit-xml-declaration="yes"/>

<xsl:param name="currentPage"/>

<xsl:template match="/">
<xsl:for-each select="$currentPage/picturesWithHeadingsLinks">

<xsl:variable name="item" select="umbraco.library:GetXmlNodeById(./content/id)"></xsl:variable>
<a href="{umbraco.library:NiceUrl($item/link)}">
<img src="{concat('/umbraco/ImageGen.ashx?image=', umbraco.library:GetMedia($item/picture, 0)/umbracoFile, '&amp;width=', styleConst:GetHomePageFeatureWidth())}" alt="{$item/heading}"></img>
<xsl:value-of select="$item/heading"/>



(Please excuse the crappy code rendering above, I really need to do some work on my own website)

Note the styleConst references above.  This effectively means that I can now style the home page using only the files inside the /styles directory.  When things change, I know I don't have to go scouting through XSLT files or elsewhere for hardcoded constants.



Broken CSSFriendly menus - why?

by Claire 7/31/2010 7:37:00 AM

This article just saved me considerable pain, but I was unable to thank the person on his blog, so I"m thanking him/her here.!AC44601E5195662B!246.entry?sa=523740893

This is exactly the kind of vexing problem that can't be planned for & has the potential to totally blow your day!



Installing BlogEngine.NET into a sub directory of umbraco

by Claire 7/14/2010 8:37:00 AM

I've just spent the last several hours working out how to do this so will now blog it in the hope that it will save myself & anyone else out there some time, as there doesn't seem to be too much out there on this subject yet.  References yes, but nothing that told me exactly how to do this, so that's what I'm putting down here.


I love Umbraco, it's brilliant.  And I want to use the packages available when I can.  The Blog4Umbraco package is fine for simple needs but for a more demanding client I have found that it's been a bit wanting in the following areas:

  1. Moderation of comments before the event.  For me this is essential even on a simple blog that allows comments, due to the massive volume of comment spam on the net now.  I think it's fair to say that comment spam has probably surpassed email spam in nuisance value, at least for anyone who runs a blog.  Even this tiny little blog, which runs on an earlier version of BlogEngine.NET suffers, I need to find the time to upgrade so I can use the promising new anti spam features of the latest BlogEngine.NET!  Time, my worst enemy!! 
  2. Categories.  Blog4Umbraco only has tags.  We want categories, each with their own rss feed so we can have one for a newsletter (which can be consumed by mailchimp for example), one for updates to our site, one for news from members, etc etc.  Each of these rss feeds can then be consumed in different ways & in different places.  Cool bananas!
  3. Importing of old posts & backdating of posts.  The fact that this is not do-able in Blog4Umbraco is a real pain in the bum.

I know that Blog4Umbraco is open source & that there is work being done to address these issues, I've looked into it myself and hope to contribute more at some point but unfortunately I just don't have the time at the moment and the client's needs come first.  I have plenty of experience with using BlogEngine.NET so I decided to have a go at using it inside an umbraco site so that we could have all the features of a blog together with Umbraco's wonderful CMS features.


These are my goals, they may not be the same as yours.

  1. The latest versions of Umbraco(4.5) & BlogEngine.NET (1.6.1), on IIS7, .NET 4.0 in integrated mode, using the default xml for the blog data.  This is what I've tested in, but I'm hoping it will work for slightly earlier versions of Umbraco too.
  2. Keep my source in source control.  For my umbraco projects, after setting up the website & installing the database, I always start a web application project in Visual Studio & copy the relevant directories from the umbraco website into it, adding Post Build events to copy my changes back into the website.  I believe this is common practice for many umbraco developers & enables me to keep my templates, css, xslt, user controls & other addons in source control.  If I'm going to run BlogEngine.NET then I want to take the same approach - the files that I'm going to be changing go into the webapp project & get copied over on build.
  3. The blog will live in a sub directory (say "/blog"!), and NOT be an application of it's own in IIS.  This is because I want to use umbraco macros in my BlogEngine master page.  In this way I can easily have common navigation for both the main site & the blog, and share css files too.  The fact that it won't be it's own application makes it slightly more complicated.
  4. Minimal changes to the BlogEngine source and minimal disruptions to the Umbraco base, we don't want to have nightmares when it comes to upgrade time.

Get it done

  1. Install your umbraco site & get it up & running.  Make a visual studio web application project for it & add some post build events to copy over the relevant directories to the website. So now you've got 2 locations - the website which IIS is serving, and your project directory which should be kept in source control.

  2. Make a new folder in the website, say /blog, and copy the contents of the BlogEngine.NET website into it.
  3. Move the 3 folders, App_Code, App_Data, App_GlobalResources to the root folder, merging with existing.
  4. Move the dlls from the blog/bin directory to the root bin directory.
  5. Move web.sitemap to the root directory
  6. Move global.asax to the root directory, open it up in a text editor & remove the Application_Error method.  This doesn't work well in IIS7 integrated mode.  I like to use Elmah for error logging in my apps anyway.
  7. Delete App_global.asax.dll from the root bin directory.  This is from umbraco but apparently isn't really necessary & is safe to remove.  It will interfere with the global.asax from blogengine, which is needed.
  8. copy /blog/web.config into the project directory & include in the visual studio project.
  9. copy /blog/themes/Standard/*.* into the project directory & include in the visual studio project.  You can rename this theme to your own at this point. 
  10. copy /App_GlobalResources into the project directory & include in the visual studio project.
  11. Add post build events to copy the files added in 8, 9 & 10 to the website directory on build.  Include the bin directory in your post build events if not already.
  12. Open up the root web.config in visual studio & cut the following web.config sections from the blog folder, paste them into the root web.config
    1. configSections/sectionGroup name="BlogEngine"
    2. BlogEngine
    3. connectionStrings if needed (not needed for XML, can delete)
    4. appSettings
    5. system.web/compilation/assemblies (if not already there)
    6. system.web/machineKey
    7. system.web/authentication (overwrite the umbraco forms element)
    8. the xml providers from membership, roles and all the sitemap providers.
  13. Delete httpModules & httpHandlers sections from both web.configs, they are not needed on IIS7 and deleting them will enable us to get more useful error messages from IIS
  14. Delete the system.webServer/validation section & change validateIntegratedModeConfiguration="true" in the root web.config.  This will give us better error messages.
  15. Change the BlogEngine.VirtualPath AppSetting to "~/blog/", or whatever directory name you're using.
  16. Add "~/blog" to the umbracoReservedUrls AppSetting.
  17. At this point the project will not build for a couple of reasons:
    1. The theme files need to be converted to the web application format.  Right click on the blog folder in VS & "Convert to Web Application".
    2. Add a reference to the BlogEngine.Core.dll that's in the website's root bin.
  18. The project should now build & you should confirm that the files were copied to the website directory
  19. There are some paths in the website project that don't use the BlogEngine.VirtualPath setting.  I changed them directly in the website folder, rather than importing them into the project and having to convert them to web application format.  This is just because I don't need to keep them in source control for any other reason & I'm trying to keep it simple.  They are:
    1. 34 occurrences of ~/admin/ in the blog/admin directory, in web.sitemap and in /widgets/Administration/widget.ascx.  Replace to ~/blog/admin.  (An easy way to do this is to open up a copy of the whole blogengine solution in visual studio & do a global replace, then copy the files back to your website folder)
  20. Check that everything works on the root site & on your blog.

One gotcha you might find along the way

After you add the blogengine httpModule called you might get an "unsupported form of compression" error.  Fix this by manually editing the App_Data/settings.xml file & switching off HttpCompression: - <enablehttpcompression>False</enablehttpcompression>.  Touch the web.config to restart the website.  (You can also switch this off via the blogengine admin panel but it's possible you might not have got as far as logging in yet).

Still to resolve

Sharing membership & role providers.  To login to blogengine you'll need to set the default providers in web.config to the xml providers, but to login to umbraco you'll need the original umbraco providers.  I haven't solved this problem yet but don't expect it to be a show stopper.  I'll update here when I have more.


At this point you should be up & going with your umbraco site still working in the root, & blogengine up & running in the blog folder.  Umbraco macros should work in the site.master for your blog theme, and you should be able to set your blog site.master to share the same css as your main site, so making the 2 sites look the same.

This is early days for me but I'm optimistic I can make it work.

Various posts on the blogengine forum including

Tags: ,

BlogEngine.NET | Umbraco

Skype, don't make the same mistake I did

by Claire 12/7/2009 7:48:00 PM

If you're thinking of buying an online number from Skype so anyone can call you, then don't do it the way I did it.  Money & time wasted. 

We recently built an office next to our house (we work at home) and our ultimate hope was that Skype might be used for our business, together with mobile phones, eliminating the need for a landline in here.

Here's how not to get a Skype online number:

  1. Try out skype first to see if you like it.  We both signed up for a free account and started using skype.  I really like it!  Started getting contacts, having conversations (chat is great, it really frees up your email inbox).  Bought a headset, the phone call quality is excellent.
  2. Buy some credit in order to make calls to non skype numbers. Having successfully trialled skype to skype calls, we decided to buy some credit and start phoning non skype numbers.  That also worked great.  The cost compares well to the landline, so the next step was to get an online number so that anyone could phone us.
  3. Open a Business Control Panel & buy some online numbers.  At this point I discovered the business control panel.  As there are 2 of us, and this is a business expense, it seemed like the right thing to do.  So I started a business control panel and bought 2 online numbers, with the intention of allocating them to our 2 existing skype accounts.  This is where it started to go horribly wrong. As I now discovered, online numbers can only be assigned to accounts created within the business control panel.  So at this point we had 2 personal accounts with credit on them, and 2 online numbers that we had paid for, which could not be used on our 2 accounts.
  4. Create Skype accounts within the business control panel for the online numbers and forward the new accounts to the preferred accounts.  The solution to the mess created in No. 3 was to create 2 skype accounts from the business control panel and assign the online numbers to them.  We did not want to discard our personal accounts because we both had credit on them, and a list of contacts who knew us.  It is also not possible to transfer credit between accounts.  So we decided to forward the new skype accounts to our preferred skype accounts.  In this way anyone who phoned the online numbers would be transferred immediately to our personal skype accounts.  Great!  The next step was to set up call forwarding to our mobiles when we are not online.
  5. Set up call forwarding to mobiles when not online.  This is the last thing we needed to be able to use skype for business, and have it forward to mobile phones when not online, or not answering.  Without this it's not a great idea to give out the number to anyone.  So I set up call forwarding to my mobile on my personal account.  So at this point I had a business account with an online number which was being forwarded to my preferred personal account which has credit, which was then being forwarded to my mobile when unanswered. Note that you need to have credit on an account in order to forward calls to non skype phones.  

Unfortunately double forwarding just doesn't work

It seemed to work sometimes, but it turns out it only works if another skype caller calls you.  Twice I contacted skype support by email and received a canned response with links to the help pages on how to setup call forwarding.  Nowhere on Skype does it tell you that this arrangement won't work.  So we sadly decided it had all been a waste of time & money and investigated VOIP through our ISP.  We still had the online numbers which we had paid for which were pretty useless, as well as the skype credit, which fortunately we could use up by making outside calls with our skype ids.

VOIP turned out to be less than perfect (expensive, bad call quality, limited support for separate phone numbers).   So in desperation I decided to have one more crack at the Skype thing.  Looking for a telephone number for Skype support, I didn't find one, but did find chat support, so started a session and got someone straight away.  Half an hour later, the helpful agent and I together came to the realisation that what I was trying to do just wasn't going to work.

So after all of this time and energy spent, and with renewed confidence in Skype support after my positive chat session, once again I decided to cough up some more cash & bought credit for the business accounts so that I could set up forwarding to my mobile phone (instead of my personal skype account).  This works perfectly. Unfortunately it means that I'll have to discard my personal account, once I've used up the credit on it, & tell all my contacts that my skype id has changed.  Sadness, I liked the one I had. 

Well atleast we've got it working now, and hopefully after a trial period we can do away with bringing the cordless phone from the house in here!  In retrospect, it seems obvious that double forwarding might cause problems, but really the whole thing came about because of Skype's limitations with transferring credit & assigning online numbers.  And the lack of documentation that double forwarding won't work, together with an obvious lack of knowledge from skype support agents just caused us to lose confidence in skype altogether.  However when it works, it works great and it's probably worth persevering with.

Tags: , , ,


Don't buy a puppy for Christmas, consider one of these beautiful homeless pooches!

by Claire 12/4/2009 10:43:00 AM

1. The Greyhound is typical of a greyhound and an adult with a lovely, gentle nature……

2. Jess (no photo yet)…..Labrador X Staffy (surrendered by a family with kids….just no longer want her)

3. Staffy X Ridgeback – advised by Ranger a lovely nature too

4. Small “purebred” brindle staffy (female I think) been impounded for almost 2 weeks and is just so sweet

5. Staffy X Large Tan – another with a friendly disposition – according to the Ranger when cleaning kennel he just comes over and sits by your side

Tags: ,

Cats & Dogs

Beautiful Miranda

by Claire 12/1/2009 4:13:00 PM


My name is Miranda, and I am approx. 4years old.    I’m a stray, I’ve been fed by a kind lady for 6 months but cannot stay any longer.  I’ve been sterilized, wormed and vet checked.    I will be vaccinated and microchipped at a discount for my new owner.

I’m very gentle, friendly, and love company - I just need to be loved and safe.  

If you would like to see me, please ring Margaret on 9291.7773.   I am living in Lesmurdie area and I urgently need a new home.

Tags: ,

Cats & Dogs

Foster homes needed for Adult cats

by Claire 11/16/2009 9:36:00 PM

Got this email today:

"This is a quick call out to ANYONE out there who can offer to take a cat into their home for a short period, usually no more than a few weeks (though can be longer in some circumstances) - here at Cat Haven we are absolutely full to bursting point with over 75 adult cats and 20 kittens currently in our care. We have so far had 15 adult cats handed in to us today and quite simply don't have anywhere to put them, so are looking for foster carers for them until more room becomes available in the kennels. Cat Haven supplies all food, litter etc for foster carers so all you need to provide is a loving home for the cat! If you think you or anyone you know is in a position to help us out, please contact the Cat Haven ASAP."

Tags: , ,

Cats & Dogs

Cropping swf to fit div of different ratio

by Claire 11/16/2009 1:44:00 PM

I found adobe's documentation very confusing here, a typo oft copied & pasted on the web just wasted over an hour of my time, grrr!!

Here's the story:  I've got a website where I'm tiling an .swf file.  As it happens the width of the div isn't an exact multiple of the width of the .swf so the last instance of the swf's got to be cropped.

The parameter of the object tag to use is "scale".  You need to set it to "noborder".  Not "noorder"!!

So, using swfobject, here's my code that crops the swf:

    <script type="text/javascript">
        var flashvars = {};
        var params = { wmode: "opaque", scale: "noborder" };
        var attributes = {};
        swfobject.embedSWF("/flash/grass.swf", "grass5", "145", "46", "9.0.0", "expressInstall.swf", flashvars, params, attributes);

Note the width of the flash file is 197px and it's cropped to 145px.  That's all that's needed. 

Now if anyone can tell me how to tile an .swf across a div without having to embed the .swf on multiple tiled divs within the main div....?

Tags: ,


Gorgeous discarded kittens needing a home

by Claire 11/9/2009 2:01:00 PM

The  pregnant  mother of these five kittens was discarded by her previous owners. She was taken in by a foster carer and had five beautiful kittens on 24 September 2009.   One has found a home, but there are still 2 female white kittens, 1 male white and 1 male tabby looking for homes.   They are all very healthy and bright, sociable and friendly.

The kittens are not free, but new owners would pay towards a prepaid sterilization/microchipping/vaccination package which we will partly subsidise.  Once the kitten is old enough it will be sterilized with nothing more to pay by the owner as all costs are covered in the up-front payment.

If you would like to see the kittens please ring Margaret on 9291.7773


Tags: ,

Cats & Dogs

Launching an umbraco website

by Claire 11/5/2009 5:59:00 AM

Here's a list of common tasks when launching a website, umbraco in particular, but a lot of these tasks are relevant to any website.

I've started this list for my own reference, it's not complete & not the last word either, but maybe it may help someone else.

1. Make sure you're in release mode

If you've got your relevant source files in a visual studio project then make sure that you build it in release mode. 

Also, ensure that debug="false" in the compilation element of web.config

The appsetting umbracoDebugMode is apparently allright to leave as "true" as it's not supposed to cause a performance hit.

2. Give the website full trust

Umbraco needs this in the web.config:

<trust level="Full" />

3. Configure a 404 page

Create a page in the website to handle 404 not found errors.  Add some navigation links to the home page, the search page or anywhere else that might be likely.  Add the node id of this page to the <errors404> element of /config/umbracoSettings.config

4. Configure a fallback error page

For any other unhandled exceptions that are not 404s you update the web.config like so:

<customErrors mode="On" defaultRedirect="~/error.aspx" />

5. Redirect the www subdomain to non-www or vice versa

Find out whether your client likes the wwws or not and then redirect accordingly, using the following syntax in /config/UrlRewriting.config.  Encourage your client to always use the preferred address in advertising and backlinks.

<add name="www" redirect="Domain" ignoreCase="true" rewriteUrlParameter="IncludeQueryStringForRewrite" virtualUrl="*)" redirectMode="Permanent" destinationUrl="$1" />

6. Redirect old pages.

If this is a site rewrite there are probably old pages to redirect.  It's important to issue 301 "permanent redirect" so that search engines will know the old pages have moved & not disappeared completely.  

Luckily umbraco makes this easy, it uses the project which can be used to make search engine friendly & people readable urls, but is also very useful for the important 301 redirects.  Simply add the relevant entries to /config/UrlRewriting.config file.  Here's an example:

<add name="about" virtualUrl="^~/index_files/Page344.htm" destinationUrl="~/about-us.aspx" redirectMode="Permanent" ignoreCase="true" />

Note that for the above example to work your site must either be running on IIS7 or you must map the .htm extension to ASP.NET under IIS6.  This is because the requested file is .htm which isn't handled by default under IIS6.  Ditto for image files & .pdfs.

Here's an example with a query string.  In this case the products had moved but google still had the old pages in it's index.  Also need to include rewriteOnlyVirtualUrls="false" to make this work as products.aspx is a real page.

  <urlrewritingnet xmlns="" rewriteOnlyVirtualUrls="false" >
      <add name="resources" virtualUrl="^~/products.aspx\?idCategory=044c6017-34c3-4454-865f-d142c1776ab6" destinationUrl="~/products.aspx?idCategory=b02e9f0b-10f6-4ec8-b462-2a9d08410817" rewriteUrlParameter="IncludeQueryStringForRewrite" redirectMode="Permanent" ignoreCase="true"/>

If you don't have access to the old website you might not know which pages to redirect, so I often just google the domain name to find out which pages google's got in it's index & redirect those.

7. Add ELMAH error logging & notifications

You need this, really!  There are some good instructions on Lee Kelleher's site, my setup differs a little in that I choose to use the same SQL server database that umbraco lives in.  To do that, you need to run SQLServer.sql on your database.  My web.config entries are shown below.

    <sectionGroup name="elmah">
      <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
      <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
      <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
      <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
    <security allowRemoteAccess="yes" />
    <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="ElmahDB" />
    <errorMail from="websiteEmailAddress" to="yourEmailAddress" />
                    // @assembly mscorlib
                    // @assembly System.Web, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
                    // @import System.IO
                    // @import System.Web

                    HttpStatusCode == 404
                    || BaseException instanceof FileNotFoundException
                    || BaseException instanceof HttpRequestValidationException
                    /* Using RegExp below (see */
                    || Context.Request.UserAgent.match(/crawler/i)                     
                    /*|| Context.Request.ServerVariables['REMOTE_ADDR'] == '' // IPv4 only*/
    <add name="ElmahDB" connectionString="server=servername;database=db;user id=userid;password=pwd" />
                Uncomment the entry below if you are using MS Ajax v1.0.x.x
                and want to capture errors during Partial Postbacks
                NB This is not required for the version of Ajax shipped with .Net Framework v3.5!
      <add name="MsAjaxDeltaErrorLog" type="Elmah.MsAjaxDeltaErrorLogModule, Elmah"/>
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
        Uncomment the entries below if error mail reporting
        and filtering is desired.
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
      <!--<add name="ErrorTweet" type="Elmah.ErrorTweetModule, Elmah"/>-->
      <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
    <authentication mode="Forms">
      <forms name="YourAuthCookie" loginUrl="umbraco/login.aspx" protection="All" path="/" />
  <location path="elmah.axd">
        <deny users="?" />

8. Give the login authorization cookie a unique name

In the web.config there's a section specifying what your login page is, if you're using forms authentication.  You should change the name of the cookie to something unique, otherwise you'll be sharing access with any other website which has the same cookie name. 

Incidentally you should also change the loginUrl to "umbraco/login.aspx" since that's the loginurl for umbraco.  Here's an example:

<forms name="AUTHWebsiteNameOrRandomString" loginUrl="umbraco/login.aspx" protection="All" path="/" />

You only need this section if you've got custom pages protected by forms authentication (such as elmah above).

9. Update the <mailSettings> element

Put some settings in here that are relevant to the production server, if indeed your website uses these settings, such as in a contact form.

10. Submit a sitemap to google

Not essential, but a good idea especially if it's a site rewrite.  Here are some suitable packages for umbraco, I use the Cultiv sitemap.

Submit the sitemap to google using google webmaster tools.

11. Google analytics. 

Add google analytics tracking code to the master template.  Set up an analytics user account for your client's email address.

12. Google maps

Don't forget to add your business to google maps, and embed the map on your website, if appropriate.