Have you ever had the need to customize json serialization and deserialization of your WCF rest endpoint? I have, in particular to replace the WCF datetime format by generalized time format. By default, most json serializers serialize datetime objects to a generalized time format, this could lead to problems if you are creating a WCF rest endpoint which only supports input of microsofts datetime implementation like this /Date(1293034567877)/. A way to solve this problem is to replace the behavior of your rest endpoint by a custom one that uses newtonsofts json serializer instead.

To do this we have to create a message formatter class by implementing IDispatchMessageFormatter:

public class NewtonsoftJsonDispatchFormatter : IDispatchMessageFormatter
{
	public void DeserializeRequest( Message message, object[] parameters ) { ... }
	public Message SerializeReply( MessageVersion messageVersion, object[] parameters, object result ) { ... }
}

This class will be using newtonsofts json serializer to serialize and deserialize messages. Next we have to create a behavior class that extends WebHttpBehavior:

public class NewtonsoftJsonBehavior : WebHttpBehavior
{
	protected override IDispatchMessageFormatter GetRequestDispatchFormatter( OperationDescription operationDescription, ServiceEndpoint endpoint )
	{
		return new NewtonsoftJsonDispatchFormatter( operationDescription, true );
	}

	protected override IDispatchMessageFormatter GetReplyDispatchFormatter( OperationDescription operationDescription, ServiceEndpoint endpoint )
	{
		return new NewtonsoftJsonDispatchFormatter( operationDescription, false );
	}
}

The only thing left to do is tell the WCF endpoint to use this new behavior. One way to do that is using your web.config to connect the new behavior to your endpoint. In order to do that we have to create a behavior extension class by extending the BehaviorExtensionElement class:

public class NewtonsoftJsonBehaviorExtension : BehaviorExtensionElement
{
	public override Type BehaviorType
	{
		get { return typeof( NewtonsoftJsonBehavior ); }
	}

	protected override object CreateBehavior()
	{
		return new NewtonsoftJsonBehavior();
	}
}

And create a content type mapper:

public class NewtonsoftJsonContentTypeMapper : WebContentTypeMapper
{
	public override WebContentFormat GetMessageFormatForContentType( string contentType )
	{
		return WebContentFormat.Raw;
	}
}

Now when configuring your binding you can simply setup an extension like this:

<extensions>
  <behaviorExtensions>
    <add name="newtonsoftJsonBehavior" type="MyApp.NameSpace.NewtonsoftJsonBehaviorExtension, MyApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  </behaviorExtensions>
</extensions>

Setup your enpoint behavior like this:

<endpointBehaviors>
  <behavior name="restEndPointBehavior">
    <webHttp helpEnabled="false" defaultBodyStyle="Bare" defaultOutgoingResponseFormat="Json" faultExceptionEnabled="false" />
    <newtonsoftJsonBehavior/>
  </behavior>
</endpointBehaviors>

Configure a webHttpBinding to use our custom contentTypeMapper:

<bindings>
  <webHttpBinding>
    <binding name="restWebHttpBinding" contentTypeMapper="MyApp.NameSpace.NewtonsoftJsonContentTypeMapper, MyApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </webHttpBinding>
</bindings>

And finally setting up our endpoint like this:

<services>
  <service name="MyApp.NameSpace.Service" behaviorConfiguration="restServiceBehavior">
    <endpoint address="" behaviorConfiguration="restEndPointBehavior" binding="webHttpBinding" bindingConfiguration="restWebHttpBinding" contract="MyApp.NameSpace.IService" />
  </service>
</services>

Your WCF rest endpoint is now using your own message formatter to serialize and deserialize messages! Below is a reallife example of using newtonsofts json serializer for this implementation. There are some other cool features when using newtonsofts json serializer. For example if your service method has a byte[] parameter, you can just post a Base64 encoded string and it will be automatically deserialized to a byte[] on the server.

public class NewtonsoftJsonDispatchFormatter : IDispatchMessageFormatter
{
	OperationDescription operation;
	Dictionary<string, int> parameterNames;

	public NewtonsoftJsonDispatchFormatter( OperationDescription operation, bool isRequest )
	{
		this.operation = operation;
		if ( isRequest )
		{
			int operationParameterCount = operation.Messages[0].Body.Parts.Count;
			if ( operationParameterCount > 1 )
			{
				this.parameterNames = new Dictionary<string, int>();
				for ( int i = 0; i < operationParameterCount; i++ )
				{
					this.parameterNames.Add( operation.Messages[0].Body.Parts[i].Name, i );
				}
			}
		}
	}

	public void DeserializeRequest( Message message, object[] parameters )
	{
		object bodyFormatProperty;
		if ( !message.Properties.TryGetValue( WebBodyFormatMessageProperty.Name, out bodyFormatProperty ) ||
			( bodyFormatProperty as WebBodyFormatMessageProperty ).Format != WebContentFormat.Raw )
		{
			throw new InvalidOperationException( "Incoming messages must have a body format of Raw. Is a ContentTypeMapper set on the WebHttpBinding?" );
		}

		var bodyReader = message.GetReaderAtBodyContents();
		bodyReader.ReadStartElement( "Binary" );
		byte[] rawBody = bodyReader.ReadContentAsBase64();
		var ms = new MemoryStream( rawBody );

		var sr = new StreamReader( ms );
		var serializer = new Newtonsoft.Json.JsonSerializer();
		if ( parameters.Length == 1 )
		{
			// single parameter, assuming bare
			parameters[0] = serializer.Deserialize( sr, operation.Messages[0].Body.Parts[0].Type );
		}
		else
		{
			// multiple parameter, needs to be wrapped
			Newtonsoft.Json.JsonReader reader = new Newtonsoft.Json.JsonTextReader( sr );
			reader.Read();
			if ( reader.TokenType != Newtonsoft.Json.JsonToken.StartObject )
			{
				throw new InvalidOperationException( "Input needs to be wrapped in an object" );
			}

			reader.Read();
			while ( reader.TokenType == Newtonsoft.Json.JsonToken.PropertyName )
			{
				var parameterName = reader.Value as string;
				reader.Read();
				if ( this.parameterNames.ContainsKey( parameterName ) )
				{
					int parameterIndex = this.parameterNames[parameterName];
					parameters[parameterIndex] = serializer.Deserialize( reader, this.operation.Messages[0].Body.Parts[parameterIndex].Type );
				}
				else
				{
					reader.Skip();
				}

				reader.Read();
			}

			reader.Close();
		}

		sr.Close();
		ms.Close();
	}

	public Message SerializeReply( MessageVersion messageVersion, object[] parameters, object result )
	{
		byte[] body;
		var serializer = new Newtonsoft.Json.JsonSerializer();

		using ( var ms = new MemoryStream() )
		{
			using ( var sw = new StreamWriter( ms, Encoding.UTF8 ) )
			{
				using ( Newtonsoft.Json.JsonWriter writer = new Newtonsoft.Json.JsonTextWriter( sw ) )
				{
					//writer.Formatting = Newtonsoft.Json.Formatting.Indented;
					serializer.Serialize( writer, result );
					sw.Flush();
					body = ms.ToArray();
				}
			}
		}

		System.ServiceModel.Channels.Message replyMessage = System.ServiceModel.Channels.Message.CreateMessage( messageVersion, operation.Messages[1].Action, new RawBodyWriter( body ) );
		replyMessage.Properties.Add( WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty( WebContentFormat.Raw ) );
		var respProp = new HttpResponseMessageProperty();
		respProp.Headers[HttpResponseHeader.ContentType] = "application/json";
		replyMessage.Properties.Add( HttpResponseMessageProperty.Name, respProp );
		return replyMessage;
	}
}

public class RawBodyWriter : BodyWriter
{
	byte[] content;
	public RawBodyWriter( byte[] content )
		: base( true )
	{
		this.content = content;
	}

	protected override void OnWriteBodyContents( XmlDictionaryWriter writer )
	{
		writer.WriteStartElement( "Binary" );
		writer.WriteBase64( content, 0, content.Length );
		writer.WriteEndElement();
	}
}

public class NewtonsoftJsonBehavior : WebHttpBehavior
{
	public override void Validate( ServiceEndpoint endpoint )
	{
		base.Validate( endpoint );

		var elements = endpoint.Binding.CreateBindingElements();
		var webEncoder = elements.Find<WebMessageEncodingBindingElement>();
		if ( webEncoder == null )
		{
			throw new InvalidOperationException( "This behavior must be used in an endpoint with the WebHttpBinding (or a custom binding with the WebMessageEncodingBindingElement)." );
		}

		foreach ( OperationDescription operation in endpoint.Contract.Operations )
		{
			this.ValidateOperation( operation );
		}
	}

	protected override IDispatchMessageFormatter GetRequestDispatchFormatter( OperationDescription operationDescription, ServiceEndpoint endpoint )
	{
		if ( this.IsGetOperation( operationDescription ) )
		{
			// no change for GET operations
			return base.GetRequestDispatchFormatter( operationDescription, endpoint );
		}

		if ( operationDescription.Messages[0].Body.Parts.Count == 0 )
		{
			// nothing in the body, still use the default
			return base.GetRequestDispatchFormatter( operationDescription, endpoint );
		}

		return new NewtonsoftJsonDispatchFormatter( operationDescription, true );
	}

	protected override IDispatchMessageFormatter GetReplyDispatchFormatter( OperationDescription operationDescription, ServiceEndpoint endpoint )
	{
		if ( operationDescription.Messages.Count == 1 || operationDescription.Messages[1].Body.ReturnValue.Type == typeof( void ) )
		{
			return base.GetReplyDispatchFormatter( operationDescription, endpoint );
		}
		else
		{
			return new NewtonsoftJsonDispatchFormatter( operationDescription, false );
		}
	}

	private void ValidateOperation( OperationDescription operation )
	{
		if ( operation.Messages.Count > 1 )
		{
			if ( operation.Messages[1].Body.Parts.Count > 0 )
			{
				throw new InvalidOperationException( "Operations cannot have out/ref parameters." );
			}
		}

		WebMessageBodyStyle bodyStyle = this.GetBodyStyle( operation );
		int inputParameterCount = operation.Messages[0].Body.Parts.Count;
		if ( !this.IsGetOperation( operation ) )
		{
			var wrappedRequest = bodyStyle == WebMessageBodyStyle.Wrapped || bodyStyle == WebMessageBodyStyle.WrappedRequest;
			if ( inputParameterCount == 1 && wrappedRequest )
			{
				throw new InvalidOperationException( "Wrapped body style for single parameters not implemented in this behavior." );
			}
		}

		var wrappedResponse = bodyStyle == WebMessageBodyStyle.Wrapped || bodyStyle == WebMessageBodyStyle.WrappedResponse;
		var isVoidReturn = operation.Messages.Count == 1 || operation.Messages[1].Body.ReturnValue.Type == typeof( void );
		if ( !isVoidReturn && wrappedResponse )
		{
			throw new InvalidOperationException( "Wrapped response not implemented in this behavior." );
		}
	}

	private WebMessageBodyStyle GetBodyStyle( OperationDescription operation )
	{
		var wga = operation.Behaviors.Find<WebGetAttribute>();
		if ( wga != null )
		{
			return wga.BodyStyle;
		}

		var wia = operation.Behaviors.Find<WebInvokeAttribute>();
		if ( wia != null )
		{
			return wia.BodyStyle;
		}

		return this.DefaultBodyStyle;
	}

	private bool IsGetOperation( OperationDescription operation )
	{
		var wga = operation.Behaviors.Find<WebInvokeAttribute>();
		if ( wga != null )
		{
			return true;
		}

		var wia = operation.Behaviors.Find();
		if ( wia != null )
		{
			return wia.Method == "HEAD";
		}

		return false;
	}
}

public class NewtonsoftJsonBehaviorExtension : BehaviorExtensionElement
{
	public override Type BehaviorType
	{
		get { return typeof( NewtonsoftJsonBehavior ); }
	}

	protected override object CreateBehavior()
	{
		return new NewtonsoftJsonBehavior();
	}
}

public class NewtonsoftJsonContentTypeMapper : WebContentTypeMapper
{
	public override WebContentFormat GetMessageFormatForContentType( string contentType )
	{
		return WebContentFormat.Raw;
	}
}


Related articles

  • Cloud Native
  • Application Navigator
  • Kubernetes Platform
  • Digital Workspace
  • Cloud Infrastructure
  • ITTS (IT Transformation Services)
  • Managed Security Operations
  • Multi-Cloud Platform
  • Backup & Disaster Recovery
Visit our knowledge hub
Visit our knowledge hub
ITQ

Let's talk!

Knowledge is key for our existence. This knowledge we use for disruptive innovation and changing organizations. Are you ready for change?

"*" indicates required fields

First name*
Last name*
Hidden