Skip to content

WCF: “Slicing” objects on serialization

David Turner asks in a comment on the blog post Polymorphism in WCF contracts if it's possible to deliberately "slice" an instance of the derived type. Instead of passing an instance of a base type to a method he wants to pass an instance of a derived type. When the instance is send to the server he only wants the part (members) of the base type to be serialized. So he asks:

How do I instruct it to just serialize the base?

Ok, here is how you can accomplish this:

I will use the Customer-Service already presented in Interfaces und abstrakte Klassen als Parameter - Teil 1 as an example (it's in german, but the code stays the same ;-) ).

First of all we need to modify the ExtendedCustomer class to extend SimpleCustomer and no longer derive from CustomerBase. It looks like this now:

C#:
  1. [DataContract]
  2. public class ExtendedCustomer : SimpleCustomer
  3. {
  4.     // body has not changed
  5. }

For "slicing" our instance we need a method that converts an instance of every class that derives from SimpleCustomer to an instance of SimpleCustomer. Therefore we add a method Slice() to the SimpleCustomer class:

C#:
  1. [DataContract]
  2. public class SimpleCustomer : CustomerBase
  3. {
  4.     internal SimpleCustomer Slice()
  5.     {
  6.         SimpleCustomer sc = new SimpleCustomer();
  7.         sc.Name = this.Name;
  8.         return sc;
  9.     }
  10. }

Now we will make use of a DataContractSurrogate. The implementation of the IDataContractSurrogate interface for our needs looks like this:

C#:
  1. class SerializeBaseTypeSurrogate : IDataContractSurrogate
  2. {
  3.     #region IDataContractSurrogate Member
  4.  
  5.     public object GetCustomDataToExport(Type clrType, Type dataContractType)
  6.     {
  7.         return null;
  8.     }
  9.  
  10.     public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo,
  11.         Type dataContractType)
  12.     {
  13.         return null;
  14.     }
  15.  
  16.     public Type GetDataContractType(Type type)
  17.     {
  18.         if (typeof(SimpleCustomer).IsAssignableFrom(type))
  19.         {
  20.             return typeof(SimpleCustomer);
  21.         }
  22.         return type;
  23.     }
  24.  
  25.     public object GetDeserializedObject(object obj, Type targetType)
  26.     {
  27.         return obj;
  28.     }
  29.  
  30.     public void GetKnownCustomDataTypes(
  31.         System.Collections.ObjectModel.Collection<Type> customDataTypes)
  32.     {
  33.         return;
  34.     }
  35.  
  36.     public object GetObjectToSerialize(object obj, Type targetType)
  37.     {
  38.         if (typeof(SimpleCustomer).IsAssignableFrom(obj.GetType()))
  39.         {
  40.             return ((SimpleCustomer)obj).Slice();
  41.         }
  42.         return obj;
  43.     }
  44.  
  45.     public Type GetReferencedTypeOnImport(string typeName, string typeNamespace,
  46.         object customData)
  47.     {
  48.         return null;
  49.     }
  50.  
  51.     public System.CodeDom.CodeTypeDeclaration ProcessImportedType(
  52.         System.CodeDom.CodeTypeDeclaration typeDeclaration,
  53.         System.CodeDom.CodeCompileUnit compileUnit)
  54.     {
  55.         return typeDeclaration;
  56.     }
  57.  
  58.     #endregion
  59. }

With the implementation of GetDataContractType we achieved a type mapping from all types that derive from SimpleCustomer to SimpleCustomer.
Site note: This is used both at serialization time as well as at schema export time.

The GetObjectToSerialize method maps any instance of a type that derives from SimpleCustomer to a SimpleCustomer instance during serialization. Therefore it uses the Slice() method of the SimpleCustomer instance.

To enable the surrogate in ServiceModel let's define an attribute called SerializeBaseTypeAttribute. The attribute implements IOperationBehavior and sets up the surrogate on the methods/operations it's applied to in the ApplyClientBehavior and ApplyDispatchBehavior methods. Here's the code:

C#:
  1. class SerializeBaseTypeAttribute : Attribute, IOperationBehavior
  2. {
  3.     #region IOperationBehavior Member
  4.     public void ApplyClientBehavior(OperationDescription operationDescription,
  5.         System.ServiceModel.Dispatcher.ClientOperation clientOperation)
  6.     {
  7.         ApplySerializeBaseTypeSurrogate(operationDescription);
  8.     }
  9.  
  10.     public void ApplyDispatchBehavior(OperationDescription operationDescription,
  11.         System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)
  12.     {
  13.         // Implements a modification or extension of the service across an operation.
  14.     }
  15.  
  16.     public void AddBindingParameters(OperationDescription operationDescription,
  17.         System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
  18.     {
  19.         // Implement this mtehod to pass data at runtime to bindings to support custom behavior.
  20.     }
  21.  
  22.  
  23.     public void Validate(OperationDescription operationDescription)
  24.     {
  25.         //Implement this method to confirm that the operation meets some intended criteria.
  26.     }
  27.     #endregion
  28.  
  29.     private static void ApplySerializeBaseTypeSurrogate(
  30.         OperationDescription description)
  31.     {
  32.         DataContractSerializerOperationBehavior dcsOperationBehavior =
  33.             description.Behaviors.Find<DataContractSerializerOperationBehavior>();
  34.         if (dcsOperationBehavior != null)
  35.         {
  36.             if (dcsOperationBehavior.DataContractSurrogate == null)
  37.                 dcsOperationBehavior.DataContractSurrogate =
  38.                     new SerializeBaseTypeSurrogate();
  39.         }
  40.     }
  41. }

Now let's add a new method AddSimpleCustomer to our service interface and implementation and apply our attribute to it. It look's like this:

C#:
  1. [ServiceContract]
  2. [ServiceKnownType(typeof(SimpleCustomer))]
  3. [ServiceKnownType(typeof(ExtendedCustomer))]
  4. public interface ICustomerService
  5. {
  6.     [OperationContract]
  7.     void AddCustomerInterface(ICustomer customer);
  8.  
  9.     [OperationContract]
  10.     void AddCustomerBase(CustomerBase customer);
  11.  
  12.     [OperationContract]
  13.     [SerializeBaseType]
  14.     void AddSimpleCustomer(SimpleCustomer customer);
  15. }

We are done now. Everytime a client passes an instance of a type which derives from SimpleCustomer only the part (members) of the base type SimpleCustomer are serialized. Hence the service will only receive instances of SimpleCustomer via this operation. The other operations still will be aware of all the other types.

Site note: With this method you can even pass instances that are not valid data contract classes. For example an instance of the following type can be passed to AddSimpleCustomer even no DataContractAttribute ist applied to it.

C#:
  1. public class CustomerWithoutDataContractAttribute : SimpleCustomer
  2. {
  3.     // body ...
  4. }

Go and give it a try ...

Any comment's appreciated.

Categories: Programming.

Tags: , ,

Comment Feed

9 Responses

  1. Hiya

    That's pretty much how I did it. Great minds think alike :-) . I was already using a surrogate for other reasons, so it wasn't too much code. Also, I didn't use a special attribute on the operations, I just made the GetObjectToSerialize method do a little more work, using reflection to decide if the actual type was a known type and assuming it needed to slice otherwise. Oh, and I created a generic class called RefBase, which implemented the Slice() method (returning T). So my transport objects all follow a similar pattern:

    [DataContract] class FooRef : RefBase { [DataMember] int id = 0; /* etc */ }
    [DataContract] class FooHeader : FooRef { /* summary info goes here for those services that need summaries */ }
    [DataContract] class Foo : FooHeader { /* adds the rest of the detail */ }

    Some additional commentary (I'll post more on my blog when I have the time):

    1. The whole [KnownType] thing sucks. Yes, it's handy when you need it, but I really haven't run into that many contracts that use polymorphic messages. I've found that one tends to write multiple operations rather than implement a choice{ } data-type. That said, ASN.1 and most of the major protocols support choice-types, so I'm glad WCF does too. But it really should understand that if I don't specify choice, then I probably want to slice! The one situation where I've found choice-types really useful is when passing changesets. But even then, it's probably better to do it as (BeginChangeset(), AddXXXChange, AddYYYChange, ..., CommitChangeset()).

    2. On a slightly different point, when you really get into multi-tier programming, AOP starts to make a whole lot of sense. I really, really don't want to re-implement my transport class on the server side and on the client side; and I'm equally opposed to putting client or server logic in the (shared) service API library. Another thing that I often want to do is aggregation (one UI facade representing multiple data objects). All these things are good'n'easy with AOP. Pity the CLR doesn't support injection (except when you start playing with ContextBoundObject, which is interesting but bad design, too special-casey, and I really am getting carried away here...).

    I've discussed (2) with my colleagues, and the best we've come up with is:

    namespace API {
    [DataContract] public class Customer {
    [DataMember] protected string name = null;
    }
    }

    namespace Server {
    public class Customer : API.Customer {
    public Customer(IDataRecord data) {
    base.name = data["Name"] as string;
    }
    }
    }

    namespace Client {
    public class Customer : API.Customer, INotifyPropertyChanged {
    public string Name { get { return base.name; } set { base.name = value; OnPropertyChanged("Name"); } }
    }
    /* etc... */
    }

    Alas, there are still many problems with this approach, which start to become apparent almost immediately.

  2. Balls, it stripped out the <T> on RefBase. I hope my meaning remains reasonably clear.

  3. Hi I'm not sure if you can help me here. I have a method that I need to return a type but serialized as a string. I want the caller to receive this string and deseralize it back to its orignal object type.

    So my question is, how do I include this type in the WSDL without having to declare it being used as a type in the ServiceContract definition?

    [DataContract]
    pubic class MyResult
    {
    [DataMember]
    public String SomeValue;
    }

    [ServiceContract]
    public interface IMyService
    {
    [ServiceOperation]
    public String MyMethod(); // return a serialized object of type MyResult
    }

    I need to ensure that the result remains encrypted and signed without having WCF involved.

    Any help would be greatly apprieciated,

    Thanks,

    Ross

  4. Hi Ross,

    I'm not sure if I really understand what you want to accomplish.
    Do you only want your type MyResult shown up in your WSDL without using the type in any of your methods?
    If you do try adding the ServiceKnownType attribute to your interface like this:

    [ServiceContract]
    [ServiceKnownType(typeof(MyResult))]
    public interface IMyService
    {
    // ...
    }

    This will lead to the following definition beeing added to your wsdl:

    <xs:schema elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/Contracts">
    <xs:complexType name="MyResult">
    <xs:sequence>
    <xs:element minOccurs="0" name="SomeValue" nillable="true" type="xs:string"/>
    </xs:sequence>
    </xs:complexType>
    <xs:element name="MyResult" nillable="true" type="tns:MyResult"/>
    </xs:schema>

    Btw: How does the caller know how to deserialize the string? You should consider changing your interface definiton!

  5. Hi I already tried to add the [ServiceKnownType(typeof(MyResult))] to the interface but it doesn't add this to the wsdl.

    Btw: How does the caller know how to deserialize the string? You should consider changing your interface definiton!

    How would I go about changing my interface definition to somehow state that I require the return string to be decrypted, verify the signature and deseralize the string to the MyResult type?

    I really appreciate you help on this. It's funny, I can do this in the old way be adding the type to the service interface, but in WCF I can' seem to find the anwser yet. I know it's possible. I might want to use the XmlSerializer in place of the DataContractSerializer and see if that works.

  6. Hi,

    adding ServiceKnownType should work. If the type doesn't show up in your wsdl take a look at imported shemas. There should be lines reading something like this:
    <wsdl:import namespace="MyServiceNamespace" location="http://localhost/MyService?wsdl=wsdl0"/>
    <xsd:import schemaLocation="http://MyService/HelloWorld?xsd=xsd0" namespace="http://schemas.microsoft.com/2003/10/Serialization/"/>
    ...

    If you need to deal with encryption/decryption and signing messages you may want to have a look at WCF's security features. Here are some links that might be a good starting point:

    http://www.code-magazine.com/Article.aspx?quickid=0611051

    http://msdn.microsoft.com/en-us/library/aa347791.aspx

    http://msdn.microsoft.com/en-us/library/aa347692.aspx

    http://www.codeplex.com/WCFSecurityGuide

  7. Thanks, I'll take a look at that.

    BTW - I can't use the ProtectionLevel attribute. When the caller recieves the result, they must put it on the queue and the object must remain encrypted. The problem with WCF's ProtectionLevel is that it's point-to-point, not end-to-end. Meaning that the message will be signed and encrypted across the wire but when the receiving WCF client recieves it, WCF will automatically decrypt and verify the signature. At least that's how it seems to work.

    I'll back to you when I find a resolution. That might be a few days the way things are going :) I'll retry the SeviceKnownType attribute one more time. but when I use the svcutil to generate the proxy, I don't find any reference to it.

    Thanks for your help thus far.

    Ross

  8. OK, I kind of figured it out.

    I used the ServiceKnowType as you suggested but added it to the ServiceOperation rather than the ServiceContract (eitherway way worked, but I wanted to be more specific).

    The reason why it wasn't showing up because my Service Contract only had one method that returned a String type. As soon as I changed the return type to Object it showed up.

    [Serializable]
    public class Ping3Result
    {
    public String Name;
    public String GID;
    }

    [ServiceContract]
    public interface IMyPingTest
    {
    [OperationContract(ProtectionLevel = ProtectionLevel.Sign)]
    [ServiceKnownType(typeof(Ping3Result))]
    Object Ping(String str);
    }

    Also, I needed to use the [Serializable] rather than [DataContract]. Again, either would have worked. But in my context, i need to serialize the object using the XmlSerilaizer, sign and encrypt the final string (object).

    And as stated before, it would have been queued into a database to be retrived by another application to decrypt, verify the signature then deseralize back to the correct object type. To make matters simpler, I could have just used XSD for that particular object as the real consumer of the object doesn't care about web services or WCF :)

    Thanks for you help!!! You lead me in the right direction.

    Ross



Some HTML is OK

or, reply to this post via trackback.

WP SlimStat