Pulled Apart - Part XIII: IMPF revised, again.
Note: This is part of a series; you can find the rest of the parts in the series index.
Pull started as a learning exercise, so I didn’t feel bad about using a new technology for a component that may not be the best choice. IMPF was one such area where I decided to use a new technology, though it hasn’t been smooth sailing – but I persisted, as it was important for me to learn.
I was working on some enhancements and bug fixes recently and ended up having to put yet another hack into IMPF to handle the technology behind the scenes. In this case, a Thread.Sleep delay was needed between when things are written and when they are read. This was a wake-up call for me, as it felt dirty and stupid. So what did I do? I decided the best course of action was to step back and think about the best way to do this—and so I ripped all the MemoryMappedFile out and the Win32 API stuff (about 200 lines of code) and replaced it with a WCF service/client implementation.
To do that, I needed the contract, which is about as simple as it can get:
[ServiceContract]
interface IDataService
{
[OperationContract]
void SendMessage(string message);
}
Next, I needed the implementation, which is also very simple in large part thanks to the bus:
class DataService : IDataService
{
public void SendMessage(string message)
{
IBus bus = Factory.Instance.Resolve<IBus>();
if (!string.IsNullOrWhiteSpace(message))
{
if (message[0] == '!')
{
switch (message)
{
case "!playunplayed":
{
bus.Broadcast(DataAction.LaunchUnplayedEpisode);
break;
}
case "!forcerefresh":
{
bus.Broadcast(DataAction.UpdateFeeds);
break;
}
}
}
else
{
// It’s a feed
bus.Broadcast(DataAction.ParseFeed, message);
}
}
}
}
You may note some command parameter items above—this is for new features I am working on.
Finally, I needed a way to create the WCF service (the server) and a client to talk to it. I am using .NET 4, which means I get access to the great AddDefaultEndpoints method, making this really simple. It figures out everything it needs for a default configuration from the URI that is passed. In my case, I pass in a net.pipe URI, so it sets up a named pipe for me.
public IPMF(string instance)
{
host = new ServiceHost(typeof(DataService), new Uri(InstanceURL(instance)));
host.AddDefaultEndpoints();
host.Open();
}
Lastly, sending a message to the named pipe is also very simple. You’ll note that I’m using a ChannelFactory here and not an actual implementation. This is because I did not add a service reference—since the application has all the information it needs internally, there’s no need for additional code to be written.
public static void SendMessageToServer(string instance, string message)
{
IDataService dataService = ChannelFactory<IDataService>.CreateChannel(new NetNamedPipeBinding(), new EndpointAddress(InstanceURL(instance)));
dataService.SendMessage(message);
}
Final Thoughts
This is the correct way to do this—and it is what you should use in real systems. Not only is it so much less code, but it also works perfectly—no need for insane hacks.
My learning with IMPF wasn’t only about the exceptionally powerful MemoryMappedFile in .NET 4, but also about when it should and shouldn’t be used.