Saturday 28 July 2012

WCF IDispatchMessageFormatter


Wcf extentions gives more flexibility to programmers.There are many extension points in wcf  like IParameterInspector  ,IDispatchMessageInspector and IDispatchMessageFormatter etc…We can add these functionality’s at different levels at operation level ,contract level ,endpoint level and at service level. In wcf there are four interfaces like IServiceBehavior ,IEndpointBehavior ,IContractBehavior and IOperationBehavior which are used to add the above extension points.

Using IDispatchMessageFormatter we can send our custom Message object to the client.There are two methods SerializeReply and DeserializeRequest .The DeserializeRequest deserializes the message into array of parameters.SerializeReply serializes a reply message from a specified message version, array of parameters ,and a return value.

 I am having different clients like Android,WindowsPhone,IPhone,Blackberry and Web application.I have to send different propertys to different clients.To solve this I had used IDispatchMessageFormatter and Newtonsoft library .

From client side user sends the User-Agent in header .
In service side we had used that User-Agent header to serialize the message.

Create the attribute classes for all the clients.

    [AttributeUsage(AttributeTargets.All)]
    public class Android : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.All)]
    public class WindowsPhone : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.All)]
    public class IPhone : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.All)]
    public class BlackBerry : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.All)]
    public class Web : Attribute
    {
    }

The following class  contains the properties for different clients,those are set with specific attributes.
       
    [DataContract]
    public class Data
    {
        [DataMember]
        [Android]
        public string AndroidSpecific { get; set; }

        [DataMember]
        [WindowsPhone]
        public string WindowsPhoneSpecific { get; set; }

        [DataMember]
        [IPhone]
        public string IPhoneSpecific { get; set; }

        [DataMember]
        [BlackBerry]
        public string BlackBerrySpecific { get; set; }

        [DataMember]
        [Web]
        public string WebSpecific { get; set; }
    }

Now the following service contains the method to return the Data object.

    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        [WebGet]
        Data Get();
    }

   public class Service : IService
   {
        public Data Get()
        {
            Data objData = new Data();

            objData.AndroidSpecific = "AndroidSpecific";
            objData.WindowsPhoneSpecific = "WindowsPhoneSpecific";
            objData.IPhoneSpecific = "IPhoneSpecific";
            objData.BlackBerrySpecific = "BlackBerrySpecific";
            objData.WebSpecific = "WebSpecific";

            return objData;
        }
 }

Here we are using the Newtonsoft library for serializing the object by watching the attributes.For this  we have to  use  the DefaultContratResolver’s  GetSerializableMembers method.In this method we can decide the serializable propertys.

public class Resolver : DefaultContractResolver
    {
        protected override List<MemberInfo> GetSerializableMembers(Type objectType)
        {
            //Get all the members.
            var allMembers = base.GetSerializableMembers(objectType);

            //User agent.
            string userAgent = string.Empty;

            //Get the user agent from client.
            userAgent = WebOperationContext.Current.IncomingRequest.Headers["User-Agent"];

            //The list of serializable members.
            List<MemberInfo> lstSerializableMembers = new List<MemberInfo>();

            //Iterate through all the members.
            foreach (var memberInfo in allMembers)
            {
                switch (userAgent)
                {
                    //For android.
                    case "android":
                        //Check whether the attribute exist or not.
                        if (memberInfo.GetCustomAttributes(typeof(Android), true).Length > 0)
                        {
                            //Add the property to list.
                            lstSerializableMembers.Add(memberInfo);
                        }
                        break;
                    //For iphone.
                    case "iphone":
                        //Check whether the attribute exist or not.
                        if (memberInfo.GetCustomAttributes(typeof(IPhone), true).Length > 0)
                        {
                            //Add the property to list.
                            lstSerializableMembers.Add(memberInfo);
                        }
                        break;
                    //For blackberry.
                    case "blackberry":
                        //Check whether the attribute exist or not.
                        if (memberInfo.GetCustomAttributes(typeof(BlackBerry), true).Length > 0)
                        {
                            //Add the property to list.
                            lstSerializableMembers.Add(memberInfo);
                        }
                        break;
                    //For windowsphone.
                    case "windowsphone":
                        //Check whether the attribute exist or not.
                        if (memberInfo.GetCustomAttributes(typeof(WindowsPhone), true).Length > 0)
                        {
                            //Add the property to list.
                            lstSerializableMembers.Add(memberInfo);
                        }
                        break;
                    //For web.
                    case "web":
                        //Check whether the attribute exist or not.
                        if (memberInfo.GetCustomAttributes(typeof(Web), true).Length > 0)
                        {
                            //Add the property to list.
                            lstSerializableMembers.Add(memberInfo);
                        }
                        break;
                    //Default.
                    default:
                        //Add the property to list.
                        lstSerializableMembers.Add(memberInfo);
                        break;
                }
            }
            //Return the list.
            return lstSerializableMembers;
        }
    }



Now we have to create the message formatter which is used  to send custom Message object to the client.For this we have to use  IDispatchMessageFormatter ‘s SerializeReply method.In which we can send custom Message object.

public class MessageFormatter : IDispatchMessageFormatter
    {
        //Formatter.
        private IDispatchMessageFormatter Formatter;

        //Costructor.
        public MessageFormatter(IDispatchMessageFormatter formatter)
        {
            //Set the Formatter here.
            this.Formatter = formatter;
        }

        public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)
        {
            //Do nothing.
        }

        public System.ServiceModel.Channels.Message SerializeReply(System.ServiceModel.Channels.MessageVersion messageVersion, object[] parameters, object result)
        {
            //Json body.
            byte[] body;

            //Json serializer.
            Newtonsoft.Json.JsonSerializer objJsonSerializer = new Newtonsoft.Json.JsonSerializer();

            //Set the ContractResolver which is used for getting serializable members based on User-Agent.
            objJsonSerializer.ContractResolver = new Resolver();

            //Memory stream.
            using (MemoryStream ms = new MemoryStream())
            {
                //Stream writer.
                using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
                {
                    //Json writer.
                    using (Newtonsoft.Json.JsonWriter objJsonWriter = new Newtonsoft.Json.JsonTextWriter(sw))
                    {
                        objJsonWriter.Formatting = Newtonsoft.Json.Formatting.Indented;

                        //Serialize the object to string.
                        objJsonSerializer.Serialize(objJsonWriter, result);

                        //Flush to writer.
                        sw.Flush();

                        //Get the json body.
                        body = ms.ToArray();
                    }
                }
            }

            //Create message object for client.
            Message replyMessage = Message.CreateMessage(messageVersion, "", new RawBodyWriter(body));

            //Add WebBodyFormatMessageProperty to message.
            replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));

            //HttpResponseMessageProperty.
            HttpResponseMessageProperty respProp = new HttpResponseMessageProperty();

            //Set the content type to application/json.
            respProp.Headers[HttpResponseHeader.ContentType] = "application/json";

            //Add HttpResponseMessageProperty to message.
            replyMessage.Properties.Add(HttpResponseMessageProperty.Name, respProp);

            //Return the message.
            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();
        }
    }


We can add this MessageFormatter at different  levels.Here we are adding the MessageFormatter at endpoint level.For this we have to use IEndpointBehaviors’s ApplyDispatchBehavior method.

public class EndpointBehavior : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            //Do nothing.
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            //Do nothing.
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            //Iterate through all the operations in the dispatch runtime.
            foreach (DispatchOperation objDispatchOperation in endpointDispatcher.DispatchRuntime.Operations)
            {
                //Set the formatter here.
                objDispatchOperation.Formatter = new MessageFormatter(objDispatchOperation.Formatter);
            }
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            //Do nothing.
        }
    }


Now host the service in console application.

//Create service host object.
ServiceHost objHost = new ServiceHost(typeof(Service), new Uri("http://localhost:1234/Service.svc/"));

            //Add endpoint with webHttpBinding.
            ServiceEndpoint ep = objHost.AddServiceEndpoint(typeof(IService), new WebHttpBinding(), "");

            //Add WebHttpBehavior to endpoint.
            ep.Behaviors.Add(new WebHttpBehavior() { HelpEnabled = true });

            //Add our custom behavior to endpoint.
            ep.Behaviors.Add(new EndpointBehavior());

            //Open service host.
            objHost.Open();

            Console.WriteLine("Service is running...");

            Console.ReadLine();

That’s it, by calling with different User-Agent’s we can get the different  propertys.

Let me know, if you have any feedback. Mail me for source code. Enjoy reading my articles…
sekhartechblog@gmail.com

1 comment:

  1. My result in SerializeReply in my MessageFormatter is always "". Can you help?

    ReplyDelete