Eric Schoonover
Eric Schoonover

Reputation: 48402

How can I manually create WCF OperationContract body type?

I'm trying to code a web service client in Silverlight for a RESTful WCF service that I have developed. In Silverlight I am constructing the body of the WebRequest using a DataContractSerializer instance.

This approach works great if there is a single argument for the OperationContract. It doesn't work so well if there are multiple arguments defined in the OperationContract. I believe this is because a dynamic type is being created by WCF that is named after the OperationContract and the members of that type are named after the parameters defined for the operation. The purpose of the dynamic type is to ensure that a single XML element exists in the message body being submitted to the WCF service... makes sense. Question is how do I construct this dynamic type myself so that I can submit it to the DataContractSerializer myself.

First example is a working example that defines a single parameter. Second example is the scenario that I am trying to solve (multiple parameters).


Example 1:

[OperationContract,
WebInvoke(Method = HttpMethodType.Post,
    BodyStyle = WebMessageBodyStyle.Bare,
    UriTemplate = "UnregisterProvider"),
WebHelp(Comment = "Unregistered the provider type with the specified URI.")]
void UnregisterProvider(RdfUri providerUri);

Code used to serialize message body:

StringBuilder msgBody = new StringBuilder(250);
using (XmlWriter xw = XmlWriter.Create(msgBody))
{
    var serializer = new DataContractSerializer(typeof(RdfUri));
    serializer.WriteObject(xw, providerUri);
}

Resulting body:

<RdfUri xmlns="http://schemas.datacontract.org/2004/07/Intellidimension.Rdf">esp:semanticserver</RdfUri>

Example 2:

[OperationContract,
WebInvoke(Method = HttpMethodType.Post,
    BodyStyle = WebMessageBodyStyle.WrappedRequest,  /* WrappedRequest must somehow signal WCF to create the anonymous type as it is required for multiple parameter OperationContracts */
    UriTemplate = "RegisterProvider"),
WebHelp(Comment = "Registered a provider type with the specified URI.")]
void RegisterProvider(PoolableEntityServiceProviderDescriptor descriptor, RdfUri providerUri);

Code used to serialize message body:

//?????

Resulting body:

<RegisterProvider xmlns="http://tempuri.org/">
  <descriptor i:type="a:SemanticServerProviderDescriptor" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:a="http://schemas.datacontract.org/2004/07/Intellidimension.RdfEntity.Service.DataContracts">
    <a:ConnectionString>Data Source=.\sqlexpress;Initial Catalog=RdfTest1;Persist Security Info=True;User ID=sa;Password=password</a:ConnectionString>
    <a:ProviderGraphUri>http://entitystore/graph-provider</a:ProviderGraphUri>
  </descriptor>
  <providerUri>esp:semanticserver</providerUri>
</RegisterProvider>

Update 1:

Here is a guy on the MSDN forums asking a similar question: Can I use DataContractSerializerOperationFormatter to format a list of parameters from client to server?

DataContractSerializerOperationFormatter is an internal class. So looks like I may have to implemented it's behavior for my client.

Update 2:

Some are asking why I am not just using the normal Silverlight WCF client generated by a service reference. The reason is that the WCF service on the server is a RESTful service. From the docs:

No analog to the WebHttpBinding provided in WCF is provided. To access pure HTTP, REST, RSS/Atom, or AJAX services from Silverlight 2, use the techniques described in Accessing HTTP-Based Services Directly, such as the WebClient class. To access ASP.NET AJAX services, see Accessing ASP.NET AJAX Services.

Upvotes: 2

Views: 3055

Answers (2)

CodingWithSpike
CodingWithSpike

Reputation: 43728

I haven't actually tried this, but this article might have what you want. He created a wrapped message by doing this:

    private void ConsumeWcfRest()
    {
        string url = "http://something/MyWebsite/tagservice";

        Tag tag1 = new Tag() { ID = 1, TagName = "test1" };
        Tag tag2 = new Tag() { ID = 2, TagName = "test2" };

        HttpWebRequest req = HttpWebRequest.Create(url + "/tags/wrapped/1") as HttpWebRequest;
        req.Method = "POST";
        string content = "<DoSomethingWrapped xmlns='http://mytagservice'>";
        content = content + DoSerialize(tag1, "tag1");
        content = content + DoSerialize(tag2, "tag2");
        content = content + "</DoSomethingWrapped>";

        // .....
    }

    private string DoSerialize(object obj, string rootName)
    {
        System.Runtime.Serialization.DataContractSerializer se;
        if (string.IsNullOrEmpty(rootName))
            se = new System.Runtime.Serialization.DataContractSerializer(obj.GetType());
        else
            se = new System.Runtime.Serialization.DataContractSerializer(obj.GetType(), rootName, "");

        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        se.WriteObject(ms, obj);
        ms.Position = 0;
        byte[] arr = new byte[ms.Length];
        ms.Read(arr, 0, Convert.ToInt32(ms.Length));
        return new System.Text.UTF8Encoding().GetString(arr);
    }

So basically he is using the normal DataContractSerializer to serialize each argument, then manually putting XML tags around that. It seems a little hacky, but it might work...

You might want to start decompiling the WCF libraries and see if you can find out how WCF does it in the first place.

Upvotes: 1

Darrel Miller
Darrel Miller

Reputation: 142124

Let me ask you a question back. Let's say you can get this working. What is the content-type of the POST request body?

You are abusing HTTP by trying to pretend that a POST can send two things. If you want to do what you are doing then why not use regular WCF service contracts. It is easy to send multiple parameters with those.

The problem with System.ServiceModel.Web is that it only 90% decouples the client from WCF. Therefore to get things to really work properly you need WCF on the client side. At which point why not stick with regular WCF service contracts.

If you really want to use HTTP as it was intended, then use a technology that will help instead of hinder.

Upvotes: 0

Related Questions