none
Writing a custom serializer. How to?

    Question

  • Hi there.

    I would like to write a custom serializer for WCF.  From what I've gathered I would inherit from XmlObjectSerializer and implement the required methods, particularly ReadObject and WriteObject.

    What I can't seem to find though is where to plug my new serializer in at.  I would like to modify the output of an OperationContract that is decorated with a WebGetAttribute.  So right now when calling a method decorated WebGetAttribute it returns the XML that the DataContractSerialzer outputted.  I would like to heavily customize the XML output (more so than the DataContractSerializer allows) and even serialize to a format that is not XML nor JSON.  Ideally, I would like to impement this across my whole application, so I don't have to do special stuff to every OperationContract. 

    What I have been trying to do to get started is write a serializer that writes out <element propertyname="id" propertyvalue="2" /> as something basic.  If I can do that, I can accoomplish what I want. 

    Is this possible?

    thx,

    -jonte
    Saturday, March 29, 2008 4:19 PM

Answers

  •  

    Sorry it took so long to respond.  I believe that you didn't add the new behavior to web.config. When I remove the behavior, I get the error message you describe above  You'll need to add a new behavior extension element to do this, like:

     

    public class FormProcessingBehaviorExtensionElement : BehaviorExtensionElement

    {

    public override System.Type BehaviorType

    {

    get { return typeof(FormProcessingBehavior); }

    }

    protected override object CreateBehavior()

    {

    return new FormProcessingBehavior();

    }

    }

     

    Then you'll need to add this extension element to config and config the behavior (I prefer to use Service config editor for this, but you can also do it manually

     

    In <system.ServiceModel>:

    <extensions>

    <behaviorExtensions>

    <add name="formProcessingBehavior" type="Microsoft.WebProgrammingModel.Samples.FormProcessingBehaviorExtensionElement, <your-dll-name>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

    </behaviorExtensions>

    </extensions>

     

    Then you add this behavior to your endpoint behavior:

    (in <services>)

    <endpoint address="" behaviorConfiguration="wasFormProcessingBehavior"

    binding="webHttpBinding" contract="Microsoft.WebProgrammingModel.Samples.Service">

     

    (in <behaviors>)

    <endpointBehaviors>

    <behavior name="wasFormProcessingBehavior">

    <formProcessingBehavior />

    </behavior>

    </endpointBehaviors>

     

    Again, it's easiest to do this with service configuration editor.

     

    Here's the entire setup process, but I think it's only (2) you're really missing.

     

    (1) Run the samples setup steps http://msdn2.microsoft.com/en-us/library/ms751527.aspx I believe that registering the assemblies with IIS uses the -i option in the vista case

     

    (2) Create A BehaviorExtensionElement for the new EndpointBehavior, so it can be added to config.  Then add the new behavior to config.  (Alternately, you could provide your ownh ServiceHostFactory, but adding the behavior extension element is simpler)

     

    (3) Turn off http get, https get in serviceBehavior, turn off httphelppage in serviceDebug behavior

     

    When I deploy the service to IIS, it works just as the self-hosted version.

    Thursday, April 10, 2008 8:57 PM

All replies

  • I wrote a blog article about serialization that covers how to implement a custom serializer:

    http://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/

     

    Check out near the bottom of the article in the section called "How to change Wcf to use a different serializer?".  Basically you just need to derive from Attribute and IOperationBevahior.  In this class you tell it to call your custom serializer.  You will want to use the majority of the code that i talk about in this section of the article but replace a couple of things with your own serializer

     

    Monday, March 31, 2008 4:35 AM
    Moderator
  • There is also a good discussion of serialization at:

     

    http://msdn2.microsoft.com/en-us/library/ms732038.aspx

     

    with advice near the bottom of how to apply your custom serializer to WCF services.

     

    Tuesday, April 01, 2008 8:37 PM
  • Thanks for the reply Dan.  I tried to implement a custom serializer using the method described.  I actually just tried to get it to throw an "NotImplementedException" where my serializer would normally do the serialization and it did not seem to work.  Then I tried putting breakpoints *everywhere* in an effort to see something light up.  No dice.

    Maybe I'm missing something?

    using System;
    using System.Data;
    using System.Configuration;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Xml.Linq;
    using System.Runtime.Serialization;

    public class MyFancySerializer : XmlObjectSerializer
    {
        public override bool IsStartObject(System.Xml.XmlDictionaryReader reader)
        {
            throw new NotImplementedException();
        }

        public override object ReadObject(System.Xml.XmlDictionaryReader reader, bool verifyObjectName)
        {
            throw new NotImplementedException();
        }

        public override void WriteEndObject(System.Xml.XmlDictionaryWriter writer)
        {
            throw new NotImplementedException();
        }

        public override void WriteObjectContent(System.Xml.XmlDictionaryWriter writer, object graph)
        {
            throw new NotImplementedException();
        }

        public override void WriteStartObject(System.Xml.XmlDictionaryWriter writer, object graph)
        {
            throw new NotImplementedException();
        }
    }

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Xml;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.ServiceModel.Channels;

    public class NetDataContractFormat : Attribute, IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
        {
        }

        public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
        {
            ReplaceDataContractSerializerOperationBehavior(description);
        }

        public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.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 MyFancySerializer();
        }

        public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
        {
            return new MyFancySerializer();
        }
    }

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    using System.ServiceModel.Web;
    using Entity;

    [ServiceContract]
    [NetDataContractFormat]
    public interface IService
    {
        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Xml, UriTemplate = "/test/{name}/")]
        [NetDataContractFormat]
        String SayHello(String name);

        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Xml, UriTemplate = "/test2/{name}/")]
        [NetDataContractFormat]
        CompositeObject SayHelloObject(String name);

    }

    public class Service : IService
    {
        public String SayHello(String name)
        {
            return String.Format("Hello {0}", name);
        }

        public CompositeObject SayHelloObject(string name)
        {
            CompositeObject result = new CompositeObject();

            result.name = name;
            result.id = 1024;
            result.created = DateTime.Now;

            return result;
        }
    }

    [DataContract(Name = "myfancyobject", Namespace = "http://www.schema.com/schema#")]
    public class CompositeObject
    {
        [DataMember(EmitDefaultValue = false, IsRequired = true, Name = "title", Order = 0)]
        public string name { get; set; }

        [DataMember(EmitDefaultValue = false, IsRequired = false, Name = "id", Order = 1)]
        public long id { get; set; }

        [DataMember(EmitDefaultValue = false, IsRequired = false, Name = "created", Order = 2)]
        public DateTime created { get; set; }

    }

    Tuesday, April 01, 2008 11:43 PM
  •  

    When I construct a client that calls both service methods, it uses the new Serializer when it invokes the CompositeObject operation.  The new OperationBehavior appears to be working.

     

    BTW - You don't need the attribute decorating the ServiceContract, just each Operation in the contract - though this does not affect the functionality.

    Wednesday, April 02, 2008 1:27 AM
  • Thanks for looking into this Mark.

    When you invoke the methods - are you going through the SOAPed calls or calling via REST?  Like http://localhost:19646/Net.Entity.Library.v1/Service.svc/test2/joe/ ?

    When calling the through REST interface I get:
    <myfancyobject xmlns="http://www.schema.com/schema#" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <title>joe</title>
    <id>1024</id>
    <created>2008-04-01T23:41:33.357-04:00</created>
    </myfancyobject>

    So are you getting a "NoImplementedException" when you call through  http://localhost:19646/TestProject1/Service.svc/test2/joe/?   I'm assuming you're using FX3.5 (you'd have to be if you were doing hte REST call)?

    best,

    -jonte
    Wednesday, April 02, 2008 3:51 AM
  • I was getting this result using SOAP - I'll experiment with REST (and yes, I'm using 3.5).

    Wednesday, April 02, 2008 9:47 PM
  • Yes, I now see the same behavior you're seeing using a REST endpoint.  My guess is that the WebGet operation behavior is replacing the new XmlObjectSerializer based on the WebMessageFormat settings.

     

    I will do some further checking, but one possible workaround would be to use the older style REST/POX service http://msdn2.microsoft.com/en-us/library/aa395208.aspx (though I haven't tried this with a custom serializer).

     

    I'm also guessing that replacing the Formatter for the operation with a custom IDispatchMessageFormatter would solve the problem (again, I haven't tried this).

     

    Thursday, April 03, 2008 4:39 AM
  • Thanks for verifying, Mark. That sounds like a reasonable hypothesis. 

    I wonder if there's away to swap in a new formatter after the WebMessageFormat has been processed?

    I would use the older-style, but I was hoping to capitalize on all the UriTemplate and HTTP verb intercepting goodness that comes baked in with the new 3.5 constructs.

    Do you have an example of a custom IDispatchMessageFormatter?  Would a custom IDispatchMessageFormatter work with the REST 3.5 constructs?

    Thursday, April 03, 2008 9:15 PM
  • In fact, there are a couple of  new extensibility points on WebHttpBehavior that allow you to replace the Formatter. Here's a sample showing the extensibility point and implementing an IDispatchMessageFormatter:

     

    http://msdn2.microsoft.com/en-us/library/bb943485.aspx

     

     

    Thursday, April 03, 2008 9:28 PM
  • Outstanding.  This looks like it'll give me what I need and even more.  It's great that you guys give access to the raw output stream - 80% of the time it's not really needed, but it's that 20% you're dead in the water without it.  I'm beginning to understand how MVC was written now, seeing this Smile

    There's no way I would have been able to figure this out on my own in any reasonable amount of time, I'm thinking the engineer(s) that designed the extensibility points wrote the sample code.  Thanks for the help.

    -josh

    Btw, if you find out a way to use a custom serializer with a REST endpoint, be sure to post - I'd be curious. 
    Friday, April 04, 2008 3:27 AM
  • Alrighty...no dice.

    I got the sample code all running and it looked like it was going to work.  So I tried to use the same code and host it in IIS vs a console hosted app.

    I get this error:

    System.InvalidOperationException: The operation 'ProcessForm' could not be loaded because 
    it has a parameter or return type of type System.ServiceModel.Channels.Message or a type
    that has MessageContractAttribute and other parameters of different types.
    When using System.ServiceModel.Channels.Message or types with MessageContractAttribute,
    the method must not use any other types of parameters.

    What I did is I used the exact code
    (not even copy and pasted - it's a library) the sample was using and just hosted in a .svc file, like this:  <%@ ServiceHost Language="C#" Debug="true" Service="Microsoft.WebProgrammingModel.Samples.Service" %>

    I set the console app as the startup project and it works.  I set the Web App as the startup app and it croaks with that error.

    I could certainly send it your way if you want, just let me know. 


    -josh

    Friday, April 04, 2008 5:59 AM
  •  

    Sorry it took so long to respond.  I believe that you didn't add the new behavior to web.config. When I remove the behavior, I get the error message you describe above  You'll need to add a new behavior extension element to do this, like:

     

    public class FormProcessingBehaviorExtensionElement : BehaviorExtensionElement

    {

    public override System.Type BehaviorType

    {

    get { return typeof(FormProcessingBehavior); }

    }

    protected override object CreateBehavior()

    {

    return new FormProcessingBehavior();

    }

    }

     

    Then you'll need to add this extension element to config and config the behavior (I prefer to use Service config editor for this, but you can also do it manually

     

    In <system.ServiceModel>:

    <extensions>

    <behaviorExtensions>

    <add name="formProcessingBehavior" type="Microsoft.WebProgrammingModel.Samples.FormProcessingBehaviorExtensionElement, <your-dll-name>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

    </behaviorExtensions>

    </extensions>

     

    Then you add this behavior to your endpoint behavior:

    (in <services>)

    <endpoint address="" behaviorConfiguration="wasFormProcessingBehavior"

    binding="webHttpBinding" contract="Microsoft.WebProgrammingModel.Samples.Service">

     

    (in <behaviors>)

    <endpointBehaviors>

    <behavior name="wasFormProcessingBehavior">

    <formProcessingBehavior />

    </behavior>

    </endpointBehaviors>

     

    Again, it's easiest to do this with service configuration editor.

     

    Here's the entire setup process, but I think it's only (2) you're really missing.

     

    (1) Run the samples setup steps http://msdn2.microsoft.com/en-us/library/ms751527.aspx I believe that registering the assemblies with IIS uses the -i option in the vista case

     

    (2) Create A BehaviorExtensionElement for the new EndpointBehavior, so it can be added to config.  Then add the new behavior to config.  (Alternately, you could provide your ownh ServiceHostFactory, but adding the behavior extension element is simpler)

     

    (3) Turn off http get, https get in serviceBehavior, turn off httphelppage in serviceDebug behavior

     

    When I deploy the service to IIS, it works just as the self-hosted version.

    Thursday, April 10, 2008 8:57 PM
  • The link provided by Mark Cowlishaw is very helpful. There is an alternative way to read in this information that does not involve the use of a new behavior. Rather the same code used in that behavior can be placed in an object extending XmlObjectSerializer -- e.g. DataContractFormSerializer. I read a few forum posts where the question was raised about where to actually activate the serialization and a lot of confusion seemed to be present on the subject.

    Consider instead that if such a serializer is written you can call System.ServiceModel.Channels.Message class method GetBody<T>( XmlObjectSerializer ) to return your object rather than dealing with the headaches of name/value pairs in string format. The whole point of WCF is to have contract conforming requests otherwise people would just go back to having a handler/extension or page serving up RESTful requests.

    This solution involves the following:

    1. Absorb the generous knowledge that Carlos Figueria posted at http://blogs.msdn.com/carlosfigueira/archive/2008/04/17/wcf-raw-programming-model-receiving-arbitrary-data.aspx
    2. Create a DataContract that uses Message as the only argument:
      • e.g. for async:
        • [OperationContract(Action = "*", ReplyAction = "*", AsyncPattern = true)]
        • IAsyncResult BeginHelloWorld(Message input, AsyncCallback IISCallback, object IISState);
        • Message EndHelloWorld(IAsyncResult asyncResult);
      • e.g. for sync:
        • [OperationContract(Action="*", ReplyAction="*")]
        • Message HelloWorld(Message input)
    3. Create a DataContractFormSerializer (whose implemention is a combination of reflection with caching for speed and the parsing outlined at http://msdn2.microsoft.com/en-us/library/bb943485.aspx). My recommendation is to make it complete by adding support for multi-part MIME encoded POST data.
    4. Call GetBody<T>( new DataContractFormSerializer(typeof(DestinationObject)) ) to retrieve a pointer to the object.

    I think it can be cleanly argued that the injection of service behavior will be more transparent than having a call to GetBody<T> at the top of each method. I think it will depend on the tastes of the implementer but hybrid/alternate solutions could include using the service behavior with a true deserializer.


    Cheers,
    Matthew Erwin
    Snaptech, LLC



    Tuesday, February 10, 2009 3:22 AM