Ahhh...and now I've found what I was looking for. Thanks to Avner Kashtan's Blog, which pointed me to Aaron Skonnard's blog...
If you've read part 1, you know I was struggling to find a way to deal with sending interfaces as parameters into a WCF service. Some (all?) SOA people would loathe dealing with types in a service, but sometimes I think it is the right call. If I know the environment of the business I'm working with is purely .NET and I'm pretty darn sure it will never be opened up for interoperability with other platforms and/or businesses, then I'd much rather deal with .NET types just as I would in a .NET client-side-only program. The reason for my thinking is because, well, type-safety is a good thing, and because I'm sure that many applications once initially thought of as needing interoperability either never make it out the door or never really end up being used by other platforms.
Anyway, enough of that. Following is the secret to interface-based programming in WCF::
Use the NetDataContractSerializer
You'll see this bit of information in various blogs and articles, but few really tie the pieces together in terms of how to actually make it work. Here I'll do my best to accomplish this.
When dealing with a WCF service, unless the parameter types you send into the service are primitive types (in which case they are automatically serializable) or have been marked with an attribute or interface of some type that make them serializable (I'm thinking the SerializableAttribute or ISerializable interface, for instance), you will have marked the type with the DataContract and DataMember attributes--which tell WCF what members of the type to serialize. This works well if you don't want to use interfaces or abstract classes for the service method's parameters because then WCF can utilize the bits from the stream to create an instance of the specific type represented by the parameter. If you use interfaces or abstract types as your service method's parameters, though, WCF has no way of determing what type to instantiate unless you declare "special" attributes on the service or service method...the details of which I covered in part 1.
The default class used for serialization/deserialization in WCF is the DataContractSerializer type. However, there is a tucked away but very helpful class that can be used instead of the DataContractSerializer: the NetDataContractSerializer. The Net portion of the name stands for ".NET," meaning "only use this serializer when you know the client is a .NET client and not a Java or some other client. The trick is in not only knowing this serializer exists, but how you enable your service to use it. What you need to do is to create an attribute that implements IOperationBehavior. IOperationBehavior is an interface you implement when you want to modify, examine, or extend an aspect of execution in a client or service application..In this case we want to modify the aspect of serialization in one or more service methods, so we create an attribute that implements IOptionBehavior:
public class NetDataContractAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
{
ReplaceDataContractSerializerOperationBehavior(description);
}
public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
{
ReplaceDataContractSerializerOperationBehavior(description);
}
public void Validate(OperationDescription description)
{
}
private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
{
DataContractSerializerOperationBehavior dcsOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (dcsOperationBehavior != null)
{
description.Behaviors.Remove(dcsOperationBehavior);
description.Behaviors.Add(new NetDataContractSerializerOperationBehavior(description));
}
}
public class NetDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
{
public NetDataContractSerializerOperationBehavior(OperationDescription operationDescription) :
base(operationDescription)
{
}
public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns,
IList<Type> knownTypes)
{
return new NetDataContractSerializer();
}
public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name,
XmlDictionaryString ns, IList<Type> knownTypes)
{
return new NetDataContractSerializer();
}
}
}
}The above code comes from Aaron Skonnard's blog.
The most important piece of the above code to understand is the two lines near the bottom where a new NetDataContractSerializer instance gets returned. It is this instance that is reponsible for serializing type information out to the service stream and reading it back in so it can then create an instance of the type that got serialized into the service.
The only thing you have to do at this point is add the attribute to whichever methods you want this to apply to. You cannot add this attribute to the service itself but instead have to put it on every single method that needs "extra" type information (i.e. those methods that do not explicity declare the specific type you pass into the method). You apply this attribute to the interface and not the actual service, like this:
[ServiceContract]
public interface PersonService
{
[OperationContract]
[NetDataContract]
string GetFullname(IPerson person);
[OperationContract]
[NetDataContract]
Name ChangeName(IPerson person);
}After you've applied this attribute to the method(s) in the service, you can pass any implementation of IPerson you want to pass to your service. I have provided an example for you to follow along.