REST XML base type question

Hi - i have defined a base type A and inherited from it to create sub-type B and C.

I have then defined a method as follows:

[WebInvoke(Method = "POST", UriTemplate = "DoSomething", BodyStyle = WebMessageBodyStyle.WrappedRequest)]
void DoSomething(A item);

The idea being i can pass Xml representing B or C to this method and then use "is" to get the appropriate sub-type and cast. I have used this a few times in ASMX services to great success.

The problem is that i don't know HOW to write the Xml to tell it it is of sub-type B or C. I vaguely remember that in ASMX there was a "basetype" attribute or something in the Xml and so long as you included that type (and any other subtype it could be) in the contract it worked great.

My guess is to pass something such as:

<item type="C">

<>.....</>

</item>

and pass this to the DoSomething() method above as the base type.

Anyone got any pointers on this?

thanks,

steven

http://livz.org

[1183 byte] By [weblivz] at [2008-1-10]
# 1

Try this:

Code Snippet

[DataContract]

[KnownType(typeof(B))]

[KnownType(typeof(C))]

class A

{

[DataMember]

public String AData { get; set; }

}

[DataContract]

class B:A

{

[DataMember]

public String BData { get; set; }

}

[DataContract]

class C:A

{

[DataMember]

public String CData { get; set; }

}

ScottSeely at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Communication Foundation (Indigo)...
# 2

Hi Scott - sorry for the delay on getting back in touch.

No, this doesn't seem to work. In the old ASMX world you needed to add the include attribute to the proxy class so that when the data was serialized it would add the information about the base type.

However, i don't know how to add this information.

e.g. AddData(A a);

<a>

<AData>someA</AData>

<BData>someB</BData>

</a>

When i pass the above Xml in as a POST to the service, i do indeed get "A". However there seems to be NO way of getting to to B even though i have BData in there which is a property of the derived B class.

It's like my REST post should do something like:

<a derived="B">

<AData>someA</AData>

<BData>someB</BData>

</a>

... but there is no way to add this that i can see.

Any ideas anyone? This is funamental to what i am doing - and OO in general Smile

Regards,

Steven

http://livz.org

weblivz at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Communication Foundation (Indigo)...
# 3

Um, this does work. The code I had is as follows:

Contract:

Code Snippet

[ServiceContract(SessionMode=SessionMode.Required)]

interface ISayHello

{

[OperationContract]

string SayHello(string name);

[OperationContract]

string GetMessageEvents( A arg);

}

Types for A and descendants:

Code Snippet

[DataContract]

[KnownType(typeof(B))]

[KnownType(typeof(C))]

class A

{

[DataMember]

public String AData { get; set; }

}

[DataContract]

class B:A

{

[DataMember]

public String BData { get; set; }

}

[DataContract]

class C:A

{

[DataMember]

public String CData { get; set; }

}

Service implementation

Code Snippet

class SayHelloService : ISayHello

{

public string SayHello(string name)

{

return string.Format("Hello, {0}!", name);

}

public string GetMessageEvents(A arg)

{

return arg.GetType().FullName;

}

}

Host code:

Code Snippet

class Program

{

static void Main()

{

System.IO.File.WriteAllText("App_tracelog.svclog", String.Empty);

using (ServiceHost serviceHost = new ServiceHost(typeof(SayHelloService), new Uri("http://localhost/Demos/")))

{

WSHttpBinding binding = new WSHttpBinding();

serviceHost.AddServiceEndpoint(typeof (ISayHello), binding, "ISayHello");

ServiceMetadataBehavior beh = new ServiceMetadataBehavior();

beh.HttpGetEnabled = true;

serviceHost.Description.Behaviors.Add(beh);

serviceHost.AddServiceEndpoint(typeof(IMetadataExchange),

MetadataExchangeBindings.CreateMexHttpBinding(), "Mex");

serviceHost.Open();

foreach (ChannelDispatcher cd in serviceHost.ChannelDispatchers)

{

Console.WriteLine(cd.Listener.Uri);

}

Console.ReadLine();

}

}

}

Calling from a client with SvcUtil generated proxy:

Code Snippet

SayHelloClient client = new SayHelloClient("WSHttpBinding_ISayHello");

try

{

Console.WriteLine(client.GetMessageEvents(new C()));

}

Creates this output in the console:

Code Snippet

MSDNForumsReceiver.C

where MSDNForumsReceiver is the namespace for the application. So, I don't see how this failed for you. I'll need some sample code of how you applied my advice to see where it falls apart.
ScottSeely at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Communication Foundation (Indigo)...
# 4

Ah, yes, but if you are using a POST rather than a http binding.

In this case you cannot reply on WCF to be the client and so you must construct the Xml yourself.

I'm interested in how to do this using the new WCF REST support, the 3.5 framework.

Regards,

Steven

http://livz.org

weblivz at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Communication Foundation (Indigo)...
# 5

Great point-- I still got this to work Smile

Service Contract/DataContracts are:

Code Block

[DataContract]

[KnownType(typeof(B))]

[KnownType(typeof(C))]

public class A

{

[DataMember]

public String AData { get; set; }

}

[DataContract]

public class B : A

{

[DataMember]

public String BData { get; set; }

}

[DataContract]

public class C : A

{

[DataMember]

public String CData { get; set; }

}

[ServiceContract]

public interface ITestService

{

[OperationContract]

[WebInvoke(Method = "POST", UriTemplate = "DoSomething", BodyStyle = WebMessageBodyStyle.WrappedRequest)]

string DoWork(A a);

}

Service code:

Code Block

public class TestService : ITestService

{

public string DoWork(A a)

{

return a != null ? a.GetType().FullName : "null";

}

}

Service Config:

Code Block

<system.serviceModel>

<behaviors>

<serviceBehaviors>

<behavior name="MSDNWeb.TestServiceBehavior">

<serviceMetadata httpGetEnabled="true" />

<serviceDebug includeExceptionDetailInFaults="false" />

</< FONT>behavior>

</< FONT>serviceBehaviors>

<endpointBehaviors>

<behavior name="WebHttp">

<webHttp/>

</< FONT>behavior>

</< FONT>endpointBehaviors>

</< FONT>behaviors>

<services>

<service behaviorConfiguration="MSDNWeb.TestServiceBehavior"

name="TestService">

<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

<endpoint address="" binding="webHttpBinding" contract="ITestService" behaviorConfiguration="WebHttp"/>

</< FONT>service>

</< FONT>services>

</< FONT>system.serviceModel>

Client is:

Calling code

Code Block

class Program

{

static void Main()

{

System.IO.File.WriteAllText("app_tracelog.svclog", String.Empty);

TestServiceClient client = new TestServiceClient(new WebHttpBinding(),

new EndpointAddress("http://localhost/MSDNWeb/TestService.svc"));

client.Endpoint.Behaviors.Add(new WebHttpBehavior());

try

{

Console.WriteLine(client.DoWork(new C()));

}

catch (Exception ex)

{

Console.WriteLine(ex.ToString());

}

client.Close();

Console.ReadLine();

}

}

Client contract (had to change the svcutil gen'd code to make things work, changes highlighted!)

Code Block

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]

[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ITestService")]

public interface ITestService

{

[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ITestService/DoWork", ReplyAction="http://tempuri.org/ITestService/DoWorkResponse")]

[ServiceKnownType(typeof(B))]

[ServiceKnownType(typeof(C))]

[WebInvoke(Method = "POST", UriTemplate = "DoSomething", BodyStyle = WebMessageBodyStyle.WrappedRequest)]

string DoWork(A a);

}

This time, the output I get is just

C

So, polymorphism is working, even with a Post verb, on .NET 3.5.

This one was fun-- thanks for pushing me a bit!

ScottSeely at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Communication Foundation (Indigo)...
# 6

> This one was fun-- thanks for pushing me a bit!

Smile Like to keep things interesting.

Almost there Scott, but the issue i have is that i am NOT using a WCF client - in a pure REST scenario any client should be able to easily call my service.

Now, using the ServiceKnownType stuff on the client clearly adds something to the request (but my Nokia mobile phone doesn't have WCF on the client). Can you get the equivalent to my code below working in your case? Note how rather than using WCF to build the Xml post request, you need to do it yourself. (I will check this out through a trace too, but if you are online it would be quicker from you Smile )

[TestMethod]
public void GetATest()
{
//set the data
UTF8Encoding enc = new UTF8Encoding();
string datatext = @"
<a xmlns=""
http://myns.org/"">
<AValue>123</AValue>
<BValue>456</BValue>
</a>";

RestPost(new Uri("http://dev/services/api/grid.Test/default.svc"), "GetA", datatext);
}


public static string RestPost(Uri target, string method, string xmlBody)
{
return RestPost("
http://myns.org/", target, method, xmlBody);
}

public static string RestPost(string uri, Uri target, string method, string xmlBody)
{
//set the data
UTF8Encoding enc = new UTF8Encoding();
string datatext = "<" + method + " xmlns=\"" + uri + "\"" + " xmlns:i=\"
http://www.w3.org/2001/XMLSchema-instance\">" + xmlBody + "</" + method + ">";
byte[] data = enc.GetBytes(datatext);

//HTTP POST query
WebRequest request = HttpWebRequest.Create(target + "/" + method);
request.Method = "POST";
request.ContentType = "text/xml";
request.ContentLength = data.Length;

Stream datasteam = request.GetRequestStream();
datasteam.Write(data, 0, data.Length);
datasteam.Close();

WebResponse response = request.GetResponse();

StreamReader rdr = new StreamReader(response.GetResponseStream());

try
{
return rdr.ReadToEnd();
}
finally
{
rdr.Close();
}
}

My classes are defined as :

using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Runtime.Serialization;

namespace Grid.Web.ServiceInterfaces.REST
{
/// <summary>
/// This interface simply provides a mechanism to check the web services for the grid have
/// been correctly initialized.
/// </summary>
[ServiceContract(Namespace = "
http://myns.org/")]
[ServiceKnownType(typeof(A))]
[ServiceKnownType(typeof(B))]
[ServiceKnownType(typeof(C))]
public interface ITest
{
//can only pass A (or B or C) and cast to B or C
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "GetA", BodyStyle = WebMessageBodyStyle.WrappedRequest)]
string GetA(A a);
}

[DataContract(Namespace = "http://myns.org/")]
public class C : A
{
[DataMember()]
public string CValue;
}

[DataContract(Namespace = "http://myns.org/")]
public class B : A
{
[DataMember()]
public string BValue;
}

[DataContract(Namespace = "http://myns.org/")]
[KnownType(typeof(B))]
[KnownType(typeof(C))]
public class A
{
[DataMember()]
public string AValue;
}
}

weblivz at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Communication Foundation (Indigo)...
# 7

Ahhh, found the soliution - and a little annoyed with myself it wasn't more obvious.

One can use the XSD type attribute to define the sub type of the Xml fragment you are submitting - this allows ANY client to call your service.

So, a non-WCF client could call your service with the following Xml.

<a xmlns="http://tempuri.org/ITestService/DoWork"

xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:type="C">

<AData>123</AData>
<BData>456</BData>

</a>

The type attribute is clearly used by the Xml serializer to infer the sub-type represented by the query - this is very nice, but wish i had seen it written down earlier Smile

Thanks Scott.

Regards,

Steven

http://livz.org

weblivz at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Communication Foundation (Indigo)...

Visual Studio Orcas

Site Classified