The Open Closed Principle

The first sun-dried bricks were made in Mesopotamia (what is now Iraq), in the ancient city of Ur in about 4000 BC.

The Romans made use of fired bricks, and the Roman legions, which operated mobile kilns, introduced bricks to many parts of the empire. Roman bricks are often stamped with the mark of the legion that supervised their production.

In 1949, a carpenter from Denmark named Ole Kirk Christiansen started selling the Lego. Like previous bricks, the Lego was immutable, and could be used to create large structures. Unlike the bricks that came before it, Legos can be detached and replaced with other Legos, allowing structures to be redesigned and extended without destroying what came before.

The Open-Closed Principle (see the wonderful Uncle Bob article) describes a way of making our code more like Legos. The basic statement is that once a class is written and tested, it should never be changed. Changing our code turns our bricks back into mud, and can create huge cascades of failure. However, code that doesn’t change can be trusted to work the way it was previously written and tested to work.

The problem is this: requirements change. How do we change our programs if we can’t change our classes?

Our classes should be:

1) Closed for Modification….the source code for a module doesn’t change. Ever.

2) Open for Extension….the behavior of the class can be added to as requirements change.

You’ve seen this done throughout .NET. Controls fire events, and when those events are fired, all sorts of changes can be made. If our classes allowed others to listen to events, we could extend their behavior without changing them. A number of other patterns, including Façade, Command, and Chain of Responsibility, as well as simple inheritance and polymorphism can be used to provide openness for extension.

The use of re-usable, unchangeable bricks can make our coding easy, just like playing with Legos.

Single Responsibility Principle

On the day I was born, the Egyptian military surged across the Suez Canal with 100,000 men and 1,350 tanks into the Sinai Peninsula. Simultaneously, the Syrian army sent 800 tanks into the Golan Heights. This simultaneous attack against a surprised Israel led to an amazing conflict over the course of the next several weeks that changed the state of affairs in the middle east.

At the time this was happening, Robert C. Martin (“Uncle Bob”) had already been in the software business for three years. Today, he is still a practicing programmer, working with code every day, and extending his knowledge of the art and skill of modern software development.

One of Uncle Bob’s largest contributions to our understanding of object oriented programming is a set of principles known as SOLID, which I’d like to share with you.

Today, I’m going to talk briefly about the Single Responsibility Principle, which is the ‘S’ in SOLID. This principle is very easy to remember. It simply states that any class that you write should only have one reason to change. We frequently change our classes after we write them, because the customer changes directions on us, or because our understanding of the technical requirements change.

Where we get into trouble is that our classes have too many things in them. Changing one thing causes a change to something else. Changing one thing requires us to re-test something else. Changing one thing requires us to change things somewhere else. We create extra work for ourselves. We create extra bugs for ourselves.

The Single Responsibility Priniciple is a discipline which can keep us from creating extra, unnecessary work for ourselves. By writing our code more simply, in smaller chunks, we can insure that WHEN change comes, it affects the smallest amount of code possible.

Uncle Bob’s book chapter on the Single Responsibility Principle provides a deeper view into the subject.

Too Easy: Instant Data Access Layer with SubSonic andSQLite

It’s frightening just how much pain I have in starting a new application project. Creating an SQL database, deploying a schema, building an ORM access layer, and putting all the pieces into source control to deploy, version, update that database, both on the development SQL Express, and the production SQL Server instances—it takes time. It takes work. The only thing worse than putting in all that effort is doing the cowboy bit with the tools and NOT doing it. Then you have various unsynchronized, untestable, messy databases and deployed versions of code. Oook!

SubSonic 3.0 introduced the SimpleRepository and Auto-Migrations. These Ruby-on-Rails-inspired features, when coupled with SQLite can make your DAL-creation tasks just disappear. Let’s give it a try!

  1. Download and install System.Data.SQLite. This distribution of SQLite is a single mixed-mode dll that contains a recent SQLite build as well as ADO.NET bindings for the database. Also in the package is a linq provider, and Visual Studio Server Explorer support.
  2. Download SubSonic 3. There’s all sorts of great stuff in this download. Three different data access models. Modifiable templates. Examples. But you only need the single dll from the binaries folder. Toss the rest in a drawer and look at it later.
  3. Add some the references to your project.
  4. Create a RepositoryFactory class:
    public class SubsonicRepositoryFactory{    public static SimpleRepository GetRepository()    {        var provider = SubSonic.DataProviders.ProviderFactory.GetProvider("Data Source=|DataDirectory|my.db", "System.Data.SQLite");        var repository = new SimpleRepository(provider, SimpleRepositoryOptions.RunMigrations);        return repository;    }}

    This isn’t strictly necessary…but I hate putting my connection strings in all those angle brackets, and prefer to detect my production/test/development environments in code rather than in XML.

  5. You’re done! Make your own POCOs and use commands like SubsonicRepositoryFactory.GetRepository().Add(myObject);
  6. Go home. You’ve worked hard. Relax.

This is good stuff, and it certainly can take some of the pressure off of starting up a new project. With data access and persistence just ‘done’, you can get to logic and value quicker. And you always have the option of upgrading to a more ‘serious’ data layer if you need it later. I’m really curious if SubSonic/SQLite can provide a reasonably performant small-to-midsize backing store to a web service application server. If your applications and various UIs all access their data through the web services, who cares what the persistence storage is, and until that back end is doing enough work to require multiple servers, how much trouble can you get into by caching your objects in memory and persisting them to disk using SQLite?

It’s also worth noting that SimpleRepository doesn’t have any wiring for child collections yet. Rob has said on StackOverflow that he intends to do some work on that, but it’s not in there yet. However, it shouldn’t be too hard to drop some convenience methods on your POCOs (extension methods if you’re worried about polluting your classes) to emit Lists or IEnumerables of linked collections.

Don’t plug your Clear Wireless Modem into yourMacintosh

Update: The most recent versions of the drivers may have solved these problems. Works for me now.

WARNING! If you plug your Clear Wireless modem into a Macintosh computer with Clear’s driver installed, the modem will be flashed with a firmware that makes it incompatible with Windows. This firmware flash is not reversible. You will need to return your modem to Clear to get a new Windows-compatible one. More details below.

I got connected with Clear Wireless in Portland 4 days ago. So far I’m very happy with the bandwidth. Connected to my laptop, I’m getting 3mbs down and 200kbps up speeds. For mobile, unlimited internet, this is fantastic. Even better, I’m getting 100-160ms packet latencies, which is about one-third of what I was getting on my cellular wireless modem.

I also purchased the Clear Spot WiFi/WWAN router. This little device is really cool, letting me share my WIMAX connection with other computers, my iPhone, or whatever. Even better, it lets me use the WIMAX modem without installing drivers. This is great because the drivers for WWAN modems are terrible….and Clear doesn’t have average quality drivers.

Windows 7 Drivers

Ok. So there aren’t Windows 7 drivers. Yet. The folks at Clear have told me I can expect to see them on Thursday, when the official launch date happens. In the meantime, install the connection manager from your CD or Driver Thumb Drive(in the box), and do a little googling for instructions on installing the older version of the drivers. Scott Hanselman wrote up something on this. You can also download the 64-bit driver from here for a couple months.

Also, be sure not to unplug the Modem before using the ‘Safely Eject’ feature of Windows….I get a BSOD every time.

The Win7 drivers work. They aren’t shiny or easy to install, but they work. That’s much better than the…

Mac OS X Drivers

When Clear says they support Mac OS X, they are…well…reaching. Their driver is marked ‘Beta’ and it shows. After the install, the Connection Manager will spend 5 minutes or so just sitting there. No status message, and a red light on the modem. Is it working? Is it doing something? Who knows. After that, it will connect, and you’re set to go. Same good service.

Unless.

If you are running Snow Leopard (Mac OS X 10.6) you get nothing. No error message. No indication of failure. No connection. It just doesn’t work. Saying Windows 7 isn’t released yet is technically true, and I understand why those drivers aren’t out. OS X 10.6 has been out since August 28th. It seems foolish that an early-adopter service like Clear doesn’t support an early-adopter OS. That’s how it is, though. Clear people have told me unofficially that we should see a 10.6 driver ‘by the end of the month’. We’ll see.

You can’t go back again

The reason for the 5 minute wait when you install your Mac drivers is apparently that the drivers are installing a fresh firmware on your modem. As I stated at the top, this firmware isn’t compatible with the Windows drivers. Nor does Clear have a flash utility available that can downgrade it. You have to return your modem for a replacement. Clear has told me that this should be free, since it’s “Their Fault”. Be nice when you call, and they should hook you up.

WiFi to the rescue

Luckily, the Clear Spot can still connect to the modem with it’s new, corrupted, Mac OS (10.5 or earlier)-only firmware. I really like the driver-less mobile internet experience. No need for the hassle of incomplete, incompatible drivers or connection managers. Just Windows 7’s shiny new WiFi connection tool and fast internets.

The Clear Spot (Cradlepoint PHS-300) is a great router, works with non-Clear broadband modems, and is $50 cheaper when you get it from Clear. The battery seems to last a bit more than 3 hours with the network in use. You can get extra batteries and a car charger from Clear, but I think an external Tekkeon battery is probably a better idea. When you get it from 3Gstore, it comes with a USB cable that I’m hoping will let me charge my Clear Spot from my laptop. One less wall wart in my kit bag.

There is a slight glitch. Most of the pictures you’ll see of the Clear Spot will show it with the modem plugged right into it. It turns out this is a bad idea. WiFi and WiMax radios seem to interfere. Make sure you leave the router on Channel 1. I also didn’t get anything approaching full speeds until I used a USB extension cable to put some space between the two devices. Once I did that, speeds were right up there.

Good support experience

Clear’s service is definitely in early-adopter mode, but I had a really excellent experience both with the store staff, and with the folks at technical support. It took me 20 minutes on the phone to figure out my Mac/PC Firmware problem, but the rep was patient and eager to help, and that is an excellent experience these days.

I like my Clear service and I recommend it if you want some better mobile bandwidth.

Adding MvcContrib SubControllers to your ASP.NET MVCProject

After a little bit of work with MVC, you get infected with the spirit of clean codeand begin to desire even more ways of eliminating repetition. You’ve got partialsand html helpers. Still you are hungry. SubControllers are the dish that will fillyou up.

Why SubControllers?

There’s a design decision here. The question is why are we designing with subcontrollers?To understand the rationale, let’s look at the qualities of the various sub-view options.

  • Partial Views – partial views are probably the most useful method for re-using viewoutput. They are very easy to set up, and are very versatile. However, they don’tcontain logic. They are intended entirely as a slave to the Action Controller. Theyget their model from the controller and simply render it. This means that the controllerneeds to know and provide everything the partial view needs. Not good for somethinglike a login status control, which is an separate concern from most controllers.
  • HTML Helpers – HTML helpers get used a lot, and they are very helpful for creatingyour own ‘controls’ to use in your pages. They do not, however, support using a viewtemplate. This means you’ve got to create them and test them with tests for emittedmarkup. This adds a lot of complexity if you are trying to do something more thancreate a html rendering function. Not a good place to insert complex view logic, likea shopping cart status widget, or anything with a table or list. The HTML helper alsostill gets it’s data from the master view/controller, so it fails at separating concerns.
  • Html.RenderAction() – RenderAction is the once and future solution to a number ofproblems. Or so I hear. I have trouble getting excited about using it right now. Currently,it is not baked into MVC, but rather is in the separate ‘MVC Futures’ assembly, whereit is unsupported. Rumor is it is buggy and unsecure. In the future it may be changedor dropped or renamed or anything. ScottGu has promised in a blog comment that itis due for inclusion in MVC 2. However, I don’t program to unreleased Microsoft products.Maybe later, but for now…
  • MvcContrib SubController – Jeff Palermo implemented subcontrollers for MvcContribto give us a working solution—now—for reusable view/controller code. Subcontrollershave their own model, ViewData, are nestable, and can use view templates for theiroutput. If you have a job that goes beyond a partial view, or a html helper, SubControllersare a peach.

Setting Up Your Project

1) Add a reference to MvcContrib.

2) Create a class called StructureMapSubControllerBinder. This is only required ifyou’re using StructureMap to do your IOC for you. You can use the base SubControllerBinderfrom MvcContrib, or create your own version for your IOC tool.

    public class StructureMapSubControllerBinder: SubControllerBinder{public override object CreateSubController(TypedestinationType){object instance = ObjectFactory.GetInstance(destinationType);if (instance == null){throw new InvalidOperationException(destinationType+ " not registered with StructureMap");}return instance;}}

     

    3) Make the SubControllerBinder your default binder.

    ModelBinders.Binders.DefaultBinder = new StructureMapSubControllerBinder();
    4) Add a reference to the MvcContrib namespace to the Pages section of Web.Config.This will save you putting a lot of namespace tags in your views.
    <add namespace="MvcContrib"/>

    Creating a SubController

    1) Create a ~/Controllers/SubControllers folder. This is completely optional. If youhave a bigger project you might want to make multiple subcontrollers folders for differentareas.

    2) Create a SubController class. The action needs to have the ‘same’ name as the class.It also subclasses from SubController.

    using System.Web.Mvc;using MvcContrib;namespace NWIS.Business.Web.Controllers.SubControllers{public class DemoSubController: SubController{public ViewResult Demo(){return View();}}}

    3) Create a View subfolder. Using the ‘Add View’ context menu from the controllerwon’t work because the ‘sub’ in the class name. Just create the folder yourself. ~/Views/Demo.

    4) Create a View. Right click on the ~/Views/Demo folder. Select Add->View. Namethe view ‘Demo’. Make it a partial view (.ascx). Go ahead an add some markup to theview. Whatever you like.

    Using the SubController in your View

    1) Add an attribute to your Controller class.

    [SubControllerActionToViewDataAttribute]

    2) Add the subcontroller as a parameter to the action you want to use it in.

    public ViewResultIndex(DemoSubController mySubCont)

    3) Place the subcontroller output into your view.

    <% ViewData.Get<Action>("mySubCont").Invoke();%>
    The name you use here is the name of the parameter to the controller action.

    That should be it. You subcontroller can output into your view, and you can useit in many, many controllers and actions. You can add dependencies directly to thesubcontroller in it’s constructor. You can also pass information from the action tothe subcontroller using properties.

    Resources

    The Best of Code Syndication

    I was recently asked about who I learn from, so I threw together this list of goodplaces to get new ideas. Most of these are the RSS Links, not the web sites. I useGoogle Reader to make sure I keep up.

    Top 3

    .NET

    General

    Security

    Funny

    Podcasts

    Using StructureMap to Build Your MVC Controllers

    The simplest things seem to be the hardest to find the up-to-date documentation on.Adding a ControllerFactory to MVC is very simple, and making it work with StructureMapis pretty quick, but do you really want to write any of that code? Of course not.Somebody else can do it.

    The MVC Contrib project has built all of the code you need to make a number of thepopular IoC frameworks work in your project. Unfortunately IoC frameworks update alot, and the MvcContrib project was finding it pretty hard to keep up with the latestversions when they compiled their builds. So that code is deprecated. Don’t use it.

    Here’s how you add StructureMap to build controllers in your MVC project:

    Create a StructureMapControllerFactory Class

    using System;using System.Web.Mvc;using System.Web.Routing;using StructureMap;namespace MvcContrib.StructureMap{public class StructureMapControllerFactory: DefaultControllerFactory{public override IControllerCreateController(RequestContext context, string controllerName){Type controllerType = base.GetControllerType(controllerName);return ObjectFactory.GetInstance(controllerType) as IController;}}}

    Add a Method to Global.asax

    private void ConfigureIoC(){ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());}

    Add a call to that method in Application_Start

    ConfigureIoC();

    ….and now we can go out for steak and exotic dancers. Your controllers will have theirdependencies injected all nice and purty.

    StructureMap: IRepository and Test Data Injection

    I Love StructureMap! It’s wonderful. What a way to compose your code together easily,precisely, and consolidatedly (!!). When I put a sentence like that together, I wonderif I even know what I’m talking about. The problem with StructureMap, IoC, and dependencyinjection really seems to be that the jargon for the patterns is so manifestlytrue that once you learn what the hell you are doing, you are completelyunable to stop talking about it in shorthand. And that shorthand makes absolutelyno sense to someone who hasn’t absorbed the patterns. Keep plugging, people. Onceyou do it, you’ll get it. Then you’ll be there and not be able toexplain to other people why you’re so right. It’s like being Tom Cruise and needingto explain scientology.

    Anyways.

    I learned two things today. The first is how to hook all of my concrete generic repositorytypes together with their interfaces. I had been adding a line of configuration foreach repository. Now my test code looks like this:

    ForRequestedType(typeof(IRepository<>)).TheDefaultIsConcreteType(typeof(ListRepository<>));

    And my production code:

    ForRequestedType(typeof(IRepository<>)).TheDefaultIsConcreteType(typeof(LlblRepository<>));

    That’s easy!

    The other thing I learned is that I can happily and easily inject data into my objectregistry for testing purposes. I have an in-memory repository implementation built.All it needs is data.

    public class MemoryDataSource{private Dictionary<Type, IQueryable> data;public MemoryDataSource(Dictionary<Type, IQueryable>data){this.data = data;}public IQueryable GetQueryable(Type type){return this.data[type];}}public class ListRepository<T>: IRepository<T>{private MemoryDataSource source;public ListRepository(MemoryDataSource source){this.source = source;}public IQueryable<T> GetSource(){return ((IQueryable<T>)source.GetQueryable(typeof(T)));}public void SaveEntity(Tentity){return;}}

    Now all I need to do is build a Dictionary<> keyed on the entity object typeand fill it up with data. Once I’ve done that, I just pass it into the StructureMapregistry like this:

    ObjectFactory.Inject<Dictionary<Type, IQueryable>>(dataSource);

    Now I can have ObjectFactory construct my object under test and it’s got just thedata I need it to have.

    Adding MVC to an existing ASP.NET Application

    ASP.NET MVC Has added a very useful new programming model to developing websites in.NET. There is hype and debate all throughout the interwebs on how great MVC is. Andit all can be yours, with nothing but a ‘File->New’ in Visual Studio….unless youalready have an ASP.NET application. In which case, you need to do a bit more work.

    1. Add project references to:
      System.Web.Abstractions
      System.Web.Mvc
      System.Web.Routing
    2. Create /Controllers, /Views, and /Views/Shared folders in your project
    3. Update web.config
      <?xml version="1.0" encoding="utf-8" ?><configuration><system.web><pages><namespaces><add namespace="System.Web.Mvc"/><add namespace="System.Web.Mvc.Ajax"/><add namespace="System.Web.Mvc.Html" /><add namespace="System.Web.Routing"/><add namespace="System.Linq"/><add namespace="System.Collections.Generic"/></namespaces></pages><compilation><assemblies><add assembly="System.Core,Version=3.5.0.0,Culture=neutral,PublicKeyToken=B77A5C561934E089"/><add assembly="System.Web.Mvc,Version=1.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35" /><add assembly="System.Web.Abstractions,Version=3.5.0.0,Culture=neutral,PublicKeyToken=31BF3856AD364E35"/><add assembly="System.Web.Routing,Version=3.5.0.0,Culture=neutral,PublicKeyToken=31BF3856AD364E35"/></assemblies></compilation><httpModules><add name="UrlRoutingModule"type="System.Web.Routing.UrlRoutingModule,System.Web.Routing,Version=3.5.0.0,Culture=neutral,PublicKeyToken=31BF3856AD364E35" /></httpModules></system.web><system.webServer><validation validateIntegratedModeConfiguration="false"/><modules runAllManagedModulesForAllRequests="true"><remove name="ScriptModule" /><remove name="UrlRoutingModule" /><add name="ScriptModule" preCondition="managedHandler"type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add name="UrlRoutingModule"type="System.Web.Routing.UrlRoutingModule,System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /></modules><handlers><remove name="WebServiceHandlerFactory-Integrated"/><remove name="ScriptHandlerFactory" /><remove name="ScriptHandlerFactoryAppServices" /><remove name="ScriptResource" /><remove name="MvcHttpHandler" /><remove name="UrlRoutingHandler" /><add name="ScriptHandlerFactory" verb="*" path="*.asmx"preCondition="integratedMode"type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add name="ScriptHandlerFactoryAppServices" verb="*"path="*_AppService.axd" preCondition="integratedMode"type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add name="ScriptResource" preCondition="integratedMode"verb="GET,HEAD" path="ScriptResource.axd"type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /><add name="MvcHttpHandler" preCondition="integratedMode"verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add name="UrlRoutingHandler"preCondition="integratedMode" verb="*" path="UrlRouting.axd"type="System.Web.HttpForbiddenHandler,System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /></handlers></system.webServer></configuration>
    4. Add the following to Global.asax.cs (create a Global.asax if you don’t already haveone)
      public static void RegisterRoutes(RouteCollectionroutes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");routes.MapRoute("Default", //Route name"{controller}/{action}/{id}", //URL with parametersnew { controller = "Home",action = "Index", id = "" } //Parameter defaults);}protected void Application_Start(){RegisterRoutes(RouteTable.Routes);}

      The above is the “standard” routing for a MVC site. It will work, but it might causeyou trouble if you want the root of your website to still go to a ‘default.aspx’.Try this line instead:

      routes.MapRoute("Default", "MVC/{controller}/{action}/{id}", new { controller = "Home",action = "Index", id = "" }
    5. Edit your .csproj file by hand
      <ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

      This will tell Visual Studio to act like this is an MVC project. You will getthe context menu items for MVC in your Controllers and Views directories.
      Note: I’ve had trouble at this point with some Visual Studio installs.If you’re having trouble with “The project type is not supported by this installation.”messages here, it may be time for a clean install in a fresh VM.
    6. Set Up IIS. If you’re using IIS7, the System.Webserver settings in your Web.Configfile should have taken care of this step. If you’re going to need to map the wildcardURL to aspnet_isapi.dll in order to get all of the MVC routing magic to work.

    7. You might want to copy of the /Views/web.config file from an existing MVC projectto prevent your views from being viewed directly, rather than through their controllers.
    8. You might also wish to create a /Scripts folder and copy over the contents ofthe /Scripts folder from native MVC project. It has the jquery and MS AJAX javascriptsthat you may be looking for if you’re following a MVC book or tutorial.

    Boy howdy. That was a good chunk of work. But now you have the infinite pleasure ofdeveloping in MVC, and your old web site should continue to work under you. Deliciousincremental development goodness.

    Resources

    Incorporating Growl For Windows into my Application

    I’m a big believer in re-use. Code I don’t aihave to write is code I don’t have todebug or maintain. When I discovered that I’d really like some notificationtoast in my Windows Forms enterprise application, I immediately started lookingfor libraries.

    The definitive toast solution for Mac OS is Growl.Growl is a separate application/control panel that runs on your system and managesnotifications from all subscribing services on your system. No more overlapping toast.It also provides a lot of filtering, control, and plugin-based extensibility. Didyou want your ‘new mail’ notifications to show up on your cell phone as SMS ratherthan on your desktop? Just do it. Want the toilet to flush when the build fails? Getan Arduino and script it up!

    Needless to say I was really chuffed to find the Growlfor Windows project. Their application is still in beta, but the developers arevery responsive, and they are checking code fixes in within hours of getting bug reportswhen they can. GfW can provide you notifications for your GMail, Outlook, Visual StudioBuilds, and the current song playing in Pandora.

    Installing Growl With My Application

    My application installs with NSIS. Addingautomatic installation of the Growl client to my installer was very quick. I grabbedthe .msi file from the Growl download, and put it within my build tree. Then I addedthe following snippet to my NSIS script:

    Section "GrowlInstall" SEC01File "GrowlWindows Deployment.msi"ExecWait 'MSIEXEC.EXE /I "$INSTDIRWindows Deployment.msi"/QB- ALLUSERS=1'SectionEnd

    That’s it for the install. However, I wanted it to uninstall cleanly as well, so Iadded the following line to the uninstall section:

    ExecWait 'MSIEXEC.EXE/x "$INSTDIRWindows Deployment.msi" /qn'

    Two caveats:

    1. You may not want to uninstall Growl automatically for the user, in case they had installedit on their own and want to uninstall your app, but keep Growl. This isn’t an issuein my business deployment, but it may be one for a public application.
    2. There may be some issues with UAC installs, and I haven’t tested this yet. I thinkI can just run my NSIS exe ‘As Administrator’ and have it work, but there may be issueswith msiexec and UAC.

    Code to Send Notifications

    First things first, lets establish some interfaces to program to, that way we canswap out implementations for testing and if our needs change.

    public interface INotificationMessage{string MessageType { get; }string MessageDescription { get; }string MessageId { get; }string MessageText { get; set; }Image MessageIcon { get; }Action AcceptMessageCallback { get; set; }Action DeclineMessageCallback { get; set; }}public interface INotificationSender{void SendMessage(INotificationMessage message);}

    Next, we’ll implement a specific Notification Message type. Each Notification Messagetype can be instantiated of a specific type so that it’s very easy to add new typesas well as very easy to use them. Each type can have an icon of it’s own.

    public class ApplicationErrorNotificationMessage: INotificationMessage{public string MessageType{ get; private set; }public string MessageDescription{ get; private set; }public string MessageId{ get; private set; }public string MessageText{ get; set; }public Image MessageIcon { get; private set;}public Action AcceptMessageCallback { get; set;}public Action DeclineMessageCallback { get; set;}public ApplicationErrorNotificationMessage(){this.MessageType = "APPLICATION_ERROR";this.MessageDescription = "ApplicationError";this.MessageId = Guid.NewGuid().ToString();this.MessageIcon = Application.Properties.Resources.AppIcon;}}

    It appears that growl caches the message icons, so you might need to restart the Growlprocess if you go changing your Notification icons.

    Finally we have our implementation of INotificationSender which registers with Growl,ensures that it is installed, and ensures that it is running.

    public class GrowlNotificationSender: INotificationSender{private GrowlConnector connector;private Image applicationIcon;private string applicationName;private INotificationMessage[] messageTypes;private Dictionary<string,INotificationMessage> sentMessages;public GrowlNotificationSender(INotificationMessage[]messageTypes,Image applicationIcon,string applicationName){this.connector = new GrowlConnector();this.messageTypes = messageTypes;this.applicationIcon = applicationIcon;this.applicationName = applicationName;this.sentMessages = new Dictionary<string,INotificationMessage>();EnsureGrowlIsRunning();RegisterWithGrowl();}private void RegisterWithGrowl(){Application thisApplication = new Application(applicationName);thisApplication.Icon = applicationIcon;List<NotificationType> notificationTypes = new List<NotificationType>();foreach (var messageType in messageTypes){NotificationType notificationType =new NotificationType(messageType.MessageType,messageType.MessageDescription);notificationType.Icon = messageType.MessageIcon;notificationTypes.Add(notificationType);}this.connector.Register(thisApplication, notificationTypes.ToArray());this.connector.NotificationCallback += new GrowlConnector.CallbackEventHandler(connector_NotificationCallback);this.connector.EncryptionAlgorithm = Cryptography.SymmetricAlgorithmType.PlainText;}public void EnsureGrowlIsRunning(){Process[] processlist = Process.GetProcesses();foreach (Process theprocess in processlist){if (theprocess.ProcessName.Equals("Growl")){ return; }}Detector detector = new Growl.CoreLibrary.Detector();if (detector.IsAvailable){System.Diagnostics.Process.Start(detector.InstallationFolder + @"Growl.exe");Thread.Sleep(1000);return;}else{throw new FileNotFoundException("Growlnot installed?");}}void connector_NotificationCallback(Response response,CallbackData callbackData){if (sentMessages[callbackData.Data] != null){if (callbackData.Result == CallbackResult.CLICK){if (sentMessages[callbackData.Data].AcceptMessageCallback!= null){sentMessages[callbackData.Data].AcceptMessageCallback.Invoke();}}else{if (sentMessages[callbackData.Data].DeclineMessageCallback!= null){sentMessages[callbackData.Data].DeclineMessageCallback.Invoke();}}sentMessages.Remove(callbackData.Data);}}public void SendMessage(INotificationMessagemessage){this.sentMessages.Add(message.MessageId, message);CallbackContext callbackContext = new CallbackContext();callbackContext.Data = message.MessageId;callbackContext.Type = message.MessageType;Notification notification = new Notification(this.applicationName,message.MessageType,message.MessageId,message.MessageDescription,message.MessageText);EnsureGrowlIsRunning();this.connector.Notify(notification, callbackContext);}}

    This implementation keeps track of sent messages, and executes callbacks on them dependingon the user’s response. If you don’t want to use one of the callbacks, just leaveit undefined.

    I haven’t found a way to get Growl to send the CLOSE message rather than the TIMEOUT,but that’s ok, since I treat them the same.

    Using The Code

    You’d probably want to wire this up with your IOC container, but here’s a manual usage.

    INotificationSender sender = new GrowlNotificationSender(new[] { new ApplicationErrorNotificationMessage()},Moneta.Properties.Resources.Crystal_128_package_network,"My Application");Thread.Sleep(1000);ApplicationErrorNotificationMessage message = new ApplicationErrorNotificationMessage{MessageText = "Hi There",AcceptMessageCallback = () => MessageBox.Show("Youclicked"),DeclineMessageCallback = () => MessageBox.Show("PayAttention!")};sender.SendMessage(message);

    It’s worth noting that the first registration of your app may take a moment for Growlto process, and that’s why I have the Sleep() in there. If your app doesn’t intendto immediately send a message after it’s first registration, that’s unnecessary.

    Resources