Mr. TA
Mr. TA

Reputation: 5359

WCF REST read URL-encoded response

I'm trying to use WCF to consume a 3rd-party REST service which responds with URL-encoded data:

a=1&b=2&c=3

I have this now:

[DataContract]
class Response { 
  [DataMember(Name="a")]
  public int A { get;set;}
  [DataMember(Name="b")]
  public int B { get;set;}
  [DataMember(Name="c")]
  public int C { get;set;}
}

[ServiceContract]
interface IService
{
  [OperationContract]
  Response Foo();
}

But it comes back with:

There was an error checking start element of object of type Response. The data at the root level is invalid. Line 1, position 1.

Upvotes: 0

Views: 1365

Answers (1)

carlosfigueira
carlosfigueira

Reputation: 87308

WCF does not "understand" forms-data content type (application/x-www-forms-urlencoded), so it won't be able to read that response directly. You can either implement a message formatter which will be able to convert that format into your contract, or you can receive the response as a Stream (which will give you the full bytes of the response), which you can decode into your class.

The code below shows how you could implement a formatter for this operation. It's not generic, but you should get the picture of what needs to be done.

public class StackOverflow_16493746
{
    [ServiceContract]
    public class Service
    {
        [WebGet(UriTemplate = "*")]
        public Stream GetData()
        {
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/x-www-form-urlencoded";
            return new MemoryStream(Encoding.UTF8.GetBytes("a=1&b=2&c=3"));
        }
    }
    [ServiceContract]
    interface IService
    {
        [WebGet]
        Response Foo();
    }
    [DataContract]
    class Response
    {
        [DataMember(Name = "a")]
        public int A { get; set; }
        [DataMember(Name = "b")]
        public int B { get; set; }
        [DataMember(Name = "c")]
        public int C { get; set; }
    }
    public class MyResponseFormatter : IClientMessageFormatter
    {
        private IClientMessageFormatter originalFormatter;

        public MyResponseFormatter(IClientMessageFormatter originalFormatter)
        {
            this.originalFormatter = originalFormatter;
        }

        public object DeserializeReply(Message message, object[] parameters)
        {
            HttpResponseMessageProperty httpResp = (HttpResponseMessageProperty)
                message.Properties[HttpResponseMessageProperty.Name];
            if (httpResp.Headers[HttpResponseHeader.ContentType] == "application/x-www-form-urlencoded")
            {
                if (parameters.Length > 0)
                {
                    throw new InvalidOperationException("out/ref parameters not supported in this formatter");
                }

                byte[] bodyBytes = message.GetReaderAtBodyContents().ReadElementContentAsBase64();
                NameValueCollection pairs = HttpUtility.ParseQueryString(Encoding.UTF8.GetString(bodyBytes));
                Response result = new Response();
                foreach (var key in pairs.AllKeys)
                {
                    string value = pairs[key];
                    switch (key)
                    {
                        case "a":
                            result.A = int.Parse(value);
                            break;
                        case "b":
                            result.B = int.Parse(value);
                            break;
                        case "c":
                            result.C = int.Parse(value);
                            break;
                    }
                }

                return result;
            }
            else
            {
                return this.originalFormatter.DeserializeReply(message, parameters);
            }
        }

        public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
        {
            throw new NotSupportedException("This is a reply-only formatter");
        }
    }
    public class MyClientBehavior : WebHttpBehavior
    {
        protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
        {
            return new MyResponseFormatter(base.GetReplyClientFormatter(operationDescription, endpoint));
        }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
        host.Open();
        Console.WriteLine("Host opened");

        ChannelFactory<IService> factory = new ChannelFactory<IService>(new WebHttpBinding(), new EndpointAddress(baseAddress));
        factory.Endpoint.Behaviors.Add(new MyClientBehavior());
        IService proxy = factory.CreateChannel();
        Response resp = proxy.Foo();
        Console.WriteLine("a={0},b={1},c={2}", resp.A, resp.B, resp.C);

        ((IClientChannel)proxy).Close();
        factory.Close();

        Console.Write("Press ENTER to close the host");
        Console.ReadLine();
        host.Close();
    }
}

Upvotes: 1

Related Questions