I just finished my activation system which is just a WCF service, finished all the unit tests and ensured they passed and met requirements. Now its time to make a test client, so I load up Visual Studio and start a simple Console Application with the service reference to IIS Express, and boom! Failure. The WCF code that consumes the service is rather simple.
static void Main(string[] args) { ActivationServiceClient client = new ActivationServiceClient(); ActivationRequest request = new ActivationRequest(); request.DateCreated = DateTime.Now; request.MachineName = Environment.MachineName; request.ProductCode = Guid.NewGuid(); request.ProductKey = "ABCDEFGHIJKLMNPQRSTUVWXYZ"; ActivationResponse response = client.Activate(request); byte[] license = response.License; string licenseHash = response.LicenseHash; ActivationResult result = response.Result; }
I highlighted line 10, because that's where the failure is. Here's the full exception.
FaultException`1 was unhandled
There was an error while trying to deserialize parameter http://tempuri.org/:request. Please see InnerException for more details.
I do the obvious and check the inner exception, but there is none. However I noticed there was a Details property with some extra data that read the following.
{An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:
System.Runtime.Serialization.InvalidDataContractException: There was an error while trying to deserialize parameter http://tempuri.org/:request. Please see InnerException for more details. ----> System.Runtime.Serialization.InvalidDataContractException: No set method for property 'ProductKey' in type 'DCOMProductions.Activation.ActivationRequest'. The class cannot be deserialized.
In short, it says that there is no set method for property `ProductKey`. This is true because the ActivationRequest structure is immutable by design. I designed the class so that the properties are initialized through constructor parameters, and the class itself is immutable. However, you will notice that on the client that consumes the service, the service reference created a type that is not immutable and has no constructor parameters. In addition, the type created by the service reference is mutable.
After thinking about the problem and doing some research I found that WCF requires two-way serialization by default. This means that by applying [DataMember] attribute on a property, it would need to have both the get and set accessors. While its not recommended to mark member fields as a DataMember, in this scenario it is the solution. Let's take a look at some code.
private string name; [DataMember] public string Name { get { return name; } }
[DataMember] private string name; public string Name { get { return name; } }
The first code example would cause the WCF service to fail because it does not allow for two-way serialization, however the second would be fine since we are decorating the backing field as the DataMember instead of the read-only property. There's really not much else to say here, but there are tradeoffs for one good practice over another. In this case we are trading the good practice of decorating properties as DataMembers for the good practice of immutable structures.