As a developer I am never suprised that the seemingly simple tasks always end up being the ones that get you; and it wasn't a suprise that WCF would have one of those small gotchas that drain your productivity and patience. Wikipedia defines a doppleganger as the following : ""Doppelgänger" has come to refer (as in German) to any double or look-alike of a person. The word is also used to describe the sensation of having glimpsed oneself in peripheral vision, in a position where there is no chance that it could have been a reflection. They are generally regarded as harbingers
of bad luck. In some traditions, a doppelgänger seen by a person's
friends or relatives portends illness or danger, while seeing one's own
doppelgänger is an omen of death." Scary right? Well it gets worse. It seems that the Doppelgänger has found it's way into your WCF datacontracts to haunt you with obscure errors and difficult debugging. Don't worry it is quite easy to get rid of your Doppelgänger problems. So let me start by first defining the problem.
WCFExample.zip (29.17 kb)
The Problem
I want to create a WCF service that allows me to retrieve an object, and then after editing the object on the client, I can send that same object back through an update method. Sounds simple enough. Let me first define my datacontract.
[DataContract]
public class Monster
{
public Monster()
{
Victims = new List();
}
[DataMember]
public string Name { get; set; }
[DataMember]
public IList Victims { get; private set; }
}
I just want to define a monster and his victims, and send him back with some new victims.The service is nothing great, it just creates a new monster and sends it back. The client code looks like the following.
using (var serviceClient = new MonsterServiceClient())
{
Console.WriteLine("The first get and update will fail.");
Monster monster = serviceClient.Get();
// Add a new victim to the list
// Will blow up
monster.Victims.Add("Joe Mama");
serviceClient.Update(monster);
}
So if you have read the comment above, you'll notice that I've already pointed out the problem. The problem is that the IList always comes through on the client as a read-only collection. Whaaaaaaaat? I didn't define it that way! This is a simple example, but if your datacontracts get more complex, like nested objects, then you can get some really scary exceptions. The scariest one I got was ExecutionEngineException, and I quote MSDN, "Execution engine errors are fatal errors that should never occur. Such
errors occur mainly when the execution engine has been corrupted or
data is missing." Holy Crap! I think I ripped a hole in the space time continuum. See below, that's supposed to be a IList but the debugger shows it as an array.

And here is the exception after I try to add my victim.

So let's get to the solution
The Solution
There are several ways to resolve this one, none that are mind blowing. You ready for the solution.... it's as simple as creating a new copy of on the client side. That's it, no voodoo, no chicken sacrifice, and no signing deals for your soul. I modified the datacontract and added a copy constructor. I know constructors are so 2.0, you are probably all about the 3.5 stuff, but constructors are still useful. Let's look at the new datacontract.
[DataContract]
public class Monster
{
public Monster()
{
Victims = new List();
}
public Monster(Monster monster)
:this()
{
Name = monster.Name;
foreach (var victim in monster.Victims)
Victims.Add(victim);
}
[DataMember]
public string Name { get; set; }
[DataMember]
public IList Victims { get; private set; }
}
See the copy constructor, the one that takes another Monster. That's the trick to get rid of that pesky
Doppelgänger. All you have to do is call the copy constructor on the client side and then you will be ok.
using (var serviceClient = new MonsterServiceClient() )
{
Console.WriteLine("The second get and update will not fail.");
Monster monster = serviceClient.Get();
// Before I call the update method
// I need to create a new monster, and
// add all the properties to it that I
// want to send back.
Monster newMonster = new Monster(monster);
// Add a new victim to the list
newMonster.Victims.Add("Joe Mama");
serviceClient.Update(newMonster);
Console.WriteLine("See it worked now!");
}
When writing those WCF services just realize that the datacontracts you are getting, might not be the datacontracts you are expecting to get. It is a simple fix, but it's also a really simple thing to overlook. Download the project to see the issue first hand.
WCFExample.zip (29.17 kb)