The Local History project: Some challenges

Some time ago I mentioned thinking about a project on Local History. This project is still in a very conceptive application. But I already know that I want to have Windows Service and a Windows UI. More on other details later, but now let's talk about communication between the Windows Service and the Windows UI.

It's possible to do some very basic communication with a Windows Service. This can be done like this:

On the Windows Service handle commands like this:

1 protected override void OnCustomCommand(int command) 2 { 3 base.OnCustomCommand(command); 4 //Do your handling. 5 }

On the Windows UI send the commands like this:

1 ServiceController sc = new ServiceController("WindowsServiceName"); 2 int customCommand = 21; 3 sc.ExecuteCommand(customCommand);

Yes you can see this are just very basic commands, you can send integers to the Windows Service that it needs to handle. I'm sure there are enough samples to find that only needs this basic commands. But I need more, much more.

How can we do more advanced communication between the Windows UI and the Windows Service?

In the old world, .NET 1.1 or .NET 2.0, we would turn to .NET Remoting using Named Pipes. But nowadays we have .NET 3.0 and even .NET 3.5, specially we have Windows Communication Foundation (WCF). I didn't have any opportunity to use WCF. So I thought why not give WCF a try using Named Pipes.

So first thing I did, was forget about the communication channel and model the service contract and data contract.

The Service Contract is still very generic and should probably need some modifications in the future, but so far this is it:

1 [ServiceContract] 2 public interface ILocalHistory 3 { 4 [OperationContract] 5 void Do(ServiceAction action); 6 }

As you can see I make use of "ServiceAction" as data. For those interested "Operation" is a very basic enumeration. For the "ServiceAction" I have a DataContract:

1 [DataContract] 2 public class ServiceAction 3 { 4 [DataMember] 5 public Operation Operation { get; set; } 6 7 [DataMember] 8 public string Path { get; set; } 9 }

The next step was some basic code to host the service:

1 ServiceHost localHistoryServiceHost = new ServiceHost(typeof(LocalHistoryWcfService)); 2 localHistoryServiceHost.Open();

After this I still needed to configure the service:

1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <services> 5 <service name="MM.LocalHistory.Service.LocalHistoryWcfService"> 6 <endpoint address="net.pipe://localhost/LocalHistory/Service" 7 binding="netNamedPipeBinding" 8 contract="MM.LocalHistory.Service.Common.ILocalHistory"/> 9 </service> 10 </services> 11 </system.serviceModel> 12 </configuration>

The very minimum configuration you need to get the service listening to a named pipe is the above. I say very minimum, because a lot of times you might want more that this.

But let me explain something first. As you can see in lines 5 and 8 a Fully Qualified Name is used to specify the service implementer and the service contract. I'm used to also specify the Assembly name, but don't do that, it would not work.

So now we have ourselves a Service providing a Named Pipe service, I thought time for a consumer, a Named Pipe client. As I'm more used to working with Webservices I thought why not add a Service Reference from within Visual Studio 2008. But I learned the hard way, you first have to add a metadata exchange service to your WCF service or else Visual Studio won't be able to generate a proxy, or even discover the service.

But the nicest thing about this is, you only have to change the configuration to add a Metadata Exchange Service (MEX). Although, it took me a while to figure out this was all I needed to do.

1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <services> 5 <service name="MM.LocalHistory.Service.LocalHistoryWcfService" 6 behaviorConfiguration="MetadataSupport"> 7 <endpoint address="net.pipe://localhost/LocalHistory/Service" 8 binding="netNamedPipeBinding" 9 contract="MM.LocalHistory.Service.Common.ILocalHistory"/> 10 <endpoint address="net.pipe://localhost/LocalHistory/Service/mex" 11 binding="mexNamedPipeBinding" 12 contract="IMetadataExchange"/> 13 </service> 14 </services> 15 <behaviors> 16 <serviceBehaviors> 17 <behavior name="MetadataSupport"> 18 <serviceMetadata/> 19 </behavior> 20 </serviceBehaviors> 21 </behaviors> 22 </system.serviceModel> 23 </configuration>

The first step is about adding a Service Behavior to the behaviors (line 16 - 19). Besides this you also need an endpoint to provide the IMetaDataExchange contract (line 10-12). I chose for an mexNamedPipeBinding because it fits nice beside the netNamedPipeBinding, but you can choose whatever binding type you like, for example: mexTcpBinding and mexHttpBinding.

Now we have set up the Metadata Exchange we can add a Service Reference to generate our proxy. Normally you can use the Discover functionality to Discover Services in your Solution, but sadly this doesn't work for Named Pipes. So instead of automatic discovery I used the url: "net.pipe://localhost/LocalHistory/Service/mex" (make sure you have the service running).

After the discovery of the service you can click the Advanced button to configure some other options. For example you can set the Collection type to be System.Collections.Generic.List instead of System.Array. Also it's the default to reuse types in all referenced assemblies. This is something I wanted to have for the old Webservices for ages, read my other blog entry about this. You don't have newly generated types for all the services types, it just uses the types found in the referenced assembly.

image image

In the Generated code example A you can see that ServiceAction is used from the references assembly instead of a custom generated type.


1 public void Do(MM.LocalHistory.Service.Common.ServiceAction action) { 2 base.Channel.Do(action); 3 }

Generated code example A: Use of a referenced assembly.

public void Do(MM.LocalHistory.UI.LocalHistoryWcfServiceInterface.ServiceAction action) { base.Channel.Do(action); }

Generated code example B: Not using a referenced assembly.

A long post around the end of the year. More about the Local History project in the future, and more about WCF I think.

Google Talk helps you translate text


I just read an article on about Translation in Google Talk. It works very good. Only the last translation should have been "Do you speak Dutch?", very strange the translation was actually a change in language even in the text.

This feature from Google could really help me since English isn't my mother language. You can just invite the bot to your Google Talk account. Use language codes. For example Dutch to English is done by the bot and English to French is done by

Reusing Class Library between Web Service and consumer

.NET and Visual Studio make it Web Service consumers very easy to generate a proxy for accessing the Web Service. It uses the WSDL to generate all the methods, even the asynchronous access methods. Also all data-structures are generated. Sadly all lists are generated like simple array's.

Wouldn't it be nice to use the same data-structures on the consumer as we can on the Web Service? I've been thinking about this for a long time, but as far as I knew it wasn't possible. Also a lot of people told me it just wasn't possible.

I've been thinking about a solution to do some of the deserialization on the generated proxy myself. But in my trial to create a solution and searching the Internet for possible solutions I found out about a feature called Schema Importer Extension. This can be used during proxy generation by wsdl.exe or from within Visual Studio 2005 (and maybe 2008).

Some time ago Jelle Druyts wrote an article: Customizing generated Web Service proxies in Visual Studio 2005. In this article he explains how the Schema Importer Extension works. Besides this he also writes an Schema Importer Extension that can be configured for all the data-structures that need to be reused. It's a very nice solution, only it's sad that I need to configure it. While it would be nice if discovering was automatically. But in this case you can see there is a solution to the problem where data-structures are shared between Web Service and consumer by using a class library.

Windows Communication Foundation already supports some kind of reusing. When adding a Service Reference instead of a Web Reference you can choose to reuse existing classes. I tried to use this with a plain ASP.NET Web Service without luck.

Practicing more on the database - Rowversion / Timestamp data type

The Transact-SQL timestamp data type is not what it looks. It's not some sort of datetime data type, it's a rowversion. Each database has a counter that is incremented for each insert or update operation on a table that has a rowversion / timestamp column. Rowversion / timestamp is generally used as a mechanism for version-stamping table rows. The rowversion is unique for one entire database.

What about rowversion or timestamp?
Timestamp is a somewhat confusing name to use. Because in SQL-2003 the timestamp data type is exactly what we know as the datetime data type in T-SQL. So I prefer to use the synonym rowversion.

What about journaling these values?
In the past, I talked about triggers for journaling your data operations. In the specific example of journaling, you have to handle a rowversion column different. Because of the characteristics of a rowversion column you cannot use this datatype to journal the values deleted and inserted into a table. However the nonnullable rowversion column is semantically equivalent to a binary(8) column (a nullable rowversion is equivalent to a varbinary(8)), so you can choose to copy the inserted and deleted data from a rowversion column into a binary(8) column.

Versioning an entire entity
As mentioned a rowversion column can be used for versioning a row in a table. But you can look further. It can also be used for versioning an entire entity. The only thing you need to check is the version column in any of the entity's rows. If one is changed act like the entire entity is changed.

I hope you have learned something about this SQL Server feature that was unknown to me for a long time. At least I've learned something new on SQL Server.