Saturday, 26 February 2011

True Names, and Other Dangers: What Dr. Seuss Can Teach Us About SoA

Did I ever tell you that Mrs. McCave, Had twenty-three sons, and she named them all Dave?

Well, she did. And that wasn't a smart thing to do; You see, when she wants one, and calls out "Yoo-Hoo!
Come into the house, Dave!" she doesn't get one; All twenty-three Daves of hers come on the run!

- from “Too Many Daves” by Dr. Seuss

They say there’s only two hard problems in software – cache invalidation, naming things, and off-by-one errors. Cache invalidation’s hard because it’s difficult to clarify requirements. Off-by-one errors are hard because the joke wouldn’t work without them. But naming things? How hard can that be?

The If you’ve ever worked in IT support, you’ll have had calls saying “the system is down”. Sometimes, a more enlightened caller will helpfully tell you that it’s ‘the network’ or ‘the database’ that’s broken. I once started at a job where everybody referred to everything as “sequel.” There had been a big database migration a few years earlier, resulting in a new website and new desktop software, and the whole process had been referred to as “upgrading to SQL Server”. Everyone kept hearing the techies talk about “upgrading to sequel”, and so when they got something new on their desktops, they conclude “Ah – this must be that sequel thing that everyone’s been talking about!”. Two days later, they call you up and say there’s a problem with ‘sequel’ – and in this context, ‘sequel’ could refer to just about anything. The name was overloaded to the point of uselessness.

“Ah yes - but it’s
services
all the way down!”

What’s scary is that this happens all over the industry. People talk about “Software as a Service”, when what they’re actually dealing with is an XML web service, that’s connecting to a WCF service hosted in a Windows service to provide a business service. Like dear old Mrs. McCave, we’re finding out that names are great if they’re unique, but when different things start laying claim to the same names, you’re going to end up cross-eyed.

So, as my team start teasing apart our proverbial big ball of mud, I’m trying out a new naming policy for the new components and modules we’re building. Pick a word that sounds nice and doesn’t mean anything within our business.

The last three projects I worked on were called Rosemary, Tarragon and Kamogelo. No namespace conflicts, no semantic overloading and no clashing with reserved keywords. Rosemary’s almost like an employee – it has an event log, and a mailbox, and a sufficiently clear sense of identity that people seem to get it. When they say there’s a problem with Rosemary, they’re right; it doesn’t take 15 minutes to work out what they mean, and that’s a good thing. It also encourages clear separation of concerns, and facilitates good discussion thereof – lots of “does this feature belong in Rosemary or Tarragon?” instead of just adding another class to the legacy codebase.

So it’s goodbye, “customer e-mail service” and “accounts system” and “web shop”, and hello to Sundance, Monolith and Aquarius. And before too long, Moonface and PuttPutt and Shadrack, in recognition of dear old Mrs. McCave.

Thursday, 24 February 2011

Making HttpContext.Current Available Within a WCF Service

I needed to add a quick’n’dirty WCF service to an ASP.NET MVC web application, so I could call a handful of methods from a different application.

The MVC app in question is using Windsor, NHibernate and the repository pattern, so we’ve got a fairly standard pattern where we spin up a ManagedWebSessionContext in the Application_BeginRequest handler (in global.asax.cs) and then flush and close the session in Application_EndRequest(). I used the Windsor WCF facility to inject a bunch of dependencies into a little WCF service, but I was finding that SessionFactory.GetCurrentSession() was always returning null – because when you’re using the ManagedWebSessionContext, your NHibernate session is bound to your HttpContext.Current, and by default you don’t have one of these inside a WCF service.

However - if you can live with tight coupling between your WCF service and IIS hosting, there’s a couple of little config things you’ll need to do to get this working. What doesn’t help is that until you’ve got all this just right, you’ll get a really helpful “Failed to Execute URL” error from IIS that’ll tell you absolutely nothing about what’s wrong.

First, make sure WCF HTTP activation is installed on your server – in Windows 2008, it’s under Server Manager –> Features:

 image

Next, make sure you’ve registered the WCF service model with IIS, by running:

C:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\> ServiceModelReg.exe –i

Next, make sure your web service is running in ASP.NET compatibility mode. First, check you’ve got this:

<system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>

in your web.config file, and then decorate your service implementation with the AspNetCompatibilityRequirements attribute:

[AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)]
public class WcfMagicService : IMagicService {
   . . .
}

The last thing I had to do was necessitated by WCF not supporting multiple host headers; I had to hard-wire the WCF endpoint to listen on a specific hostname. In this case, this involved tweaking the serviceHostingEnvironment section of web.config, which now looks like this:

<serviceHostingEnvironment aspNetCompatibilityEnabled="true">
    <baseAddressPrefixFilters>
        <add prefix=http://services.mydomain.com” />
    </baseAddressPrefixFilters>
</serviceHostingEnvironment>

And then adding another attribute to the service implementation class:

[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]
[AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)]
public class WcfMagicService : IMagicService {
}

Once that’s done, you’ll have an instantiated HttpContext.Current inside your service methods, so your code – and useful things like NHibernate’s ManagedWebSessionContext – will behave just as they do in normal MVC controllers or WebForms code-behind.

Monday, 21 February 2011

Check Out smtp4dev if You Build Mail-Enabled Software on Windows

One of my cow-orkers pointed me at a great little utility a while back called smtp4dev. It’s an SMTP server that listens on your local machine, and instead of relaying e-mail, it’ll capture them and store them in a queue so you can review and open them.

image

It’s brilliant – simple and elegant and incredibly easy to use. Just configure your application (website, debugger, logging framework – whatever it is you’re building) to send mail on localhost:25, fire up smtp4dev, and watch the messages pile up. I’ve been building SMTP appenders for log4net this evening, and it’s been really, really useful.

Binaries and source are at smtp4dev.codeplex.com – well worth a look if you ever write software that sends e-mail.

Monday, 7 February 2011

How to host Git in the same Apache server that comes with CollabNet Subversion

This is the moon rising over the Costa Smeralda, Sardinia. It has nothing to do with revision control.CollabNet Subversion Edge is a great Subversion distro that includes Apache 2.2 and the ViewVC web-based repo browser, and makes it really, really easy to get up and running with Subversion and WebDAV. I’m setting up a project server to host something we’re working on, and it’s been generally decided that whilst Subversion is all very well for keeping Word documents in, we’d quite like something a touch more… distributed for the actual source code repo. And when James Gregory mentioned on Twitter that git would mean “no more tree conflicts ”, I may have actually started salivating… ahem.

Anyway, yes. Apparently Git is quite good.

Jeremy Skinner has some fantastic notes on how to get git up and running with Apache 2.2. on a Windows server – I followed these pretty much to the letter to get my first incarnation up and running, but had to comment out a bunch of the Collabnet/Subversion settings in the Apache config files to get the Git server running properly. A bit of tinkering, though, and I’m pretty much there. What makes this interesting is that CollabNet includes a web-based admin console, which makes configuring the built-in modules very straightforward, but it does mean several of the config files have this rather ominous warning at the top:

#
# DO NOT EDIT THIS FILE IT WILL BE REGENERATED AUTOMATICALLY BY COLLABNET SUBVERSION 
#

so – any changes we make in there will be just peachy until someone touches the web interface, at which point BOOM! they’ll spontaneously stop working. So whatever we’re going to do, we need to do it without touching any of those files. I wasn’t sure at first whether this would be possible, but it seems to be up and running now and hanging together quite nicely.

Fire up the Apache httpd.conf file in your favourite editor – by default it’ll be in C:\Program Files\Subversion\data\conf\ – and add the following lines at the end:

# Configure Apache to listen for named virtual hosts on port 80
NameVirtualHost *:80

# Include the configuration file for our git http hosting
Include "C:\Program Files\Subversion\data\conf\git_httpd.conf"

Now create a new document called – yep - C:\Program Files\Subversion\data\conf\git_httpd.conf – and make it look something like this:

# HTTP settings for using Apache with MSysGit on Windows
# Based on Jeremy Skinner's notes at
http://www.jeremyskinner.co.uk/2010/07/31/hosting-a-git-server-under-apache-on-windows/

<VirtualHost *:80>

    # Set this to the root folder containing your Git repositories.
    SetEnv GIT_PROJECT_ROOT D:/Git/
   
    # Set this to export all projects by default (by default,
    # git will only publish those repositories that contain a
    # file named “git-daemon-export-ok”

    SetEnv GIT_HTTP_EXPORT_ALL
   
    # Route specific URLS matching this regular expression to the git http server.
    ScriptAliasMatch "(?x)^/git/(.*/(HEAD | info/refs | \
        objects/(info/[^/]+ | [0-9a-f]{2}/[0-9a-f]{38} | pack/pack-[0-9a-f]{40}\.(pack|idx)) | \
        git-(upload|receive)-pack))$" \
        "C:/Program Files (x86)/git/libexec/git-core/git-http-backend.exe/$1"
   
    # The canonical DNS hostname that you want to use for your git server
    ServerName my_git_server
    
    # Any other DNS aliases that point to your git server
    ServerAlias my_git_server my_git_server.mydomain.com my_git_server.my_intranet.local
    
    # The root folder for non-GIT-hosted documents (e.g. phpgit or some other Web front end)    
    DocumentRoot "D:\gitserver\htdocs\"
    <Location />
        # This section is duplicated from the Collabnet SVN LDAP authentication
        AuthType Basic
        AuthName "Spotlight GIT Repository"
        AuthBasicProvider csvn-file-users ldap-users
        Require valid-user
    </Location>
</VirtualHost>

Check your configuration by running httpd.exe from the command line, like so:

C:\Program Files\Subversion\bin>httpd.exe -f "c:\program files\Subversion\data\conf\httpd.conf" –t
Syntax OK

and if all looks good, go into services.msc and restart the CollabNetSubversionServer service (which is actually Apache)

Finally, I followed Jeremy’s instructions to get GitPhp running, but then replaced it with a different project – also called GitPhp – from http://www.xiphux.com/programming/gitphp/, which provides a full repository browser, revision history, etc.

All I had to do to get GitPHP running was to copy the gitphp.conf.php.example file to gitphp.conf.php, and then tweak the following settings:

/* The root folder of my Git repositories */
$gitphp_conf['projectroot'] = 'D:\git';

/* On 64-bit Windows, C:\Program Files (x86) ends up as C:\Progra~2\ so these need to be configured manually */
$gitphp_conf['gitbin']  = "C:\Progra~2\Git\bin\git.exe";
$gitphp_conf['diffbin']  = "C:\Progra~2\Git\bin\diff.exe";

Job done. I now have:

and that’s all on one box, with two IP addresses, with the svn and git servers sharing an instance of Apache on one address, the IIS server running on the other, and DNS records pointing svn and git at the first address and IIS at the second.

Running IIS and Apache on the same Windows 2008 R2 Server

I’m trying to get a Composite C1 site and the Apache WebDAV front-end to Subversion running on the same Windows 2008 R2 server, and doing so requires a bit of trickery with IP address bindings and such, and I thought I’d share it – partly ‘cos it’s useful, and partly because I’m bound to have to do this again in three months time and there’s no way I’ll remember how I did it. First off, make sure your box has (at least) two IP addresses – I’ve bound mine to 192.168.0.13 and 192.168.0.14
To get IIS to listen on ONLY 192.168.0.13, you’ll need to run the netsh.exe utility.
C:\Users\dylan.beattie>netsh
netsh>http add iplisten ipaddress=192.168.0.13
IP address successfully added
netsh>http show iplisten
IP addresses present in the IP listen list:
-------------------------------------------
    192.168.0.13
netsh>exit
(note that netsh.exe is a Windows 2008 utility – if you’re running Windows 2003 or earlier, look up the docs on using httpcfg.exe to achieve the same thing)
If you now fire up a web browser and go to http://192.168.0.13/, you should get the default IIS7 “Welcome” screen, and http://192.168.0.14/ shouldn’t return anything at all. Now to get Apache listening on 192.168.0.14. Find your httpd.conf file – if you’ve just installed CollabNet Subversion (like I have) it’ll be in the \data\conf folder of wherever you put your SVN install.
You’ll need to find the Listen directive in httpd.conf, and modify it to say:
Listen 192.168.0.14:80
That’s all. Next time – to get Git running on the same Apache installation… until then, happy hacking.

UPDATE: After running this for several years, I've found that occasionally, following an unscheduled shutdown or power outage, IIS won't come back up properly after the box is restarted. Sites will respond on http://localhost/ but trying to access them via hostname gives an ERR_CONNECTION_RESET message.
This can be fixed by removing and re-adding the HTTP binding:
C:\>netsh
netsh> http
netsh http> delete iplisten 192.168.0.13netsh http> add iplisten 192.168.0.13netsh http> exit

C:\>iisreset