PDA

View Full Version : Web Service Proxies and custom objects


dsmalley
11-03-2007, 08:18 PM
I have three web services. Each one has one method, which returns one of three objects. The first service returns a strongly-typed dataset. The second returns a custom object called ComplexObjectA, and the third service returns a custom object called ComplexObjectB. All three services are implementing interfaces, and exported using WebServiceExporter. On the client side, I am using proxies generated by WebServiceProxyFactory. I am not using the VS 2005 generated proxies, because I like having the proxy implement a service interface and return the real types.

The first two services and clients work fine. The client calls the method on the proxy, and it returns either the strongly-typed dataset or ComplexObjectA. For the third service, the proxy method always returns null (instead of ComplexObjectB).

I have checked the response from the third service, and it appears to be correct, with the return type defined in the WSDL and the correct XML being generated. In fact, I can create a VS 2005 proxy for this service, and it generates a new proxy object for ComplexObjectB which has all the correct data. But the Spring-generated proxy always returns null.

Now, of course, a strongly-typed dataset explicitly implements IXmlSerializable, and so does my ComplexObjectA (the one which works). ComplexObjectA includes nested dictionaries, which aren't serializable by default, so I had to write my own serialization code, including generating an explicit schema. ComplexObjectB has nested collections too, but they are lists, and so I use attributes from System.Xml.Serialization. Again, ComplexObjectB is xml serializable; I have also tested it explicitly with the XmlSerializer, and it serializes and de-serializes correctly.

So my question is: does the Spring-generated proxy require types to be either built-ins (ints, strings, etc.) or ones which explicitly implement IXmlSerializable? It is the only explanation I can think of.

I suppose I should point out explicitly that the generated proxy never throughs any kind of exception, it just returns null.


Dave

dsmalley
11-03-2007, 11:52 PM
Well, I think I have answered my own question. Unless I am really missing something, it appears the Spring-generated proxy expectx types which implement IXmlSerializable. I created a dummy type, which was just a string and a bool, with public get/set properties, and applied the attributes, and once again, the proxy returned null. I then explicitly impelemented IXmlSerializable, and the proxy returned the object with the values I set on the server side.

There is a note in the section on web service clients that you must assume responsibility for your types being serializable, but it doesn't mention that you must implement IXmlSerializable. Is there any reason the following type should not work with a Spring-generated proxy:

[Serializable()]
[XmlRoot("MyObject", Namespace = "http://my/namespace")]
[XmlType("MyObject", Namespace = "http://my/namespace")]
public class MyObject
{
private string myString;
private bool myBoolean;

[XmlElement("MyString")]
public string MyString
{
get { return myString; }
set { myString = value; }
}

[XmlElement("MyBoolean")]
public bool MyBoolean
{
get { return myBoolean; }
set { myBoolean = value; }
}
}

Dave

Bruno Baia
11-08-2007, 12:35 AM
Hi,

MyObject type is xml serialisable by default (no complex type like dictionaries, etc...) so you don't need all those attributes. Anyway it should work with them too, but maybe there is a pb here with the namespace or the XmlRoot.

What i usually do when I got complex types like your ComplexObjectB, it's to generate VS web service proxy to see what looks like the object generates, and use it for my domain. Then I'll be sure my type is XmlSerialisable.


- Bruno

dsmalley
11-08-2007, 09:30 AM
Unerglaublich.

Yes, it is the namespace. Basically, the type needs to either have no namespace specified, or have the same namespace as the exported service.

When implementing IXmlSerializable, basically out of laziness, I specified only local names for the elements and attributes when reading or writing, so it worked fine, even though the schema speciied a namespace for the type.

When relying on the default xml serialization, the generated WSDL puts the custom type in the web service's target namespace, so when the deserialization takes place, it is trying to deserialize into a type with a different namespace, and there is no match.

Now here's the rub: When you create a web service the "normal" way in VS 2005, if you use a type with a namespace assigned via either XmlRoot or XmlType, that namespace is imported and the WSDL defines that type in the specified namespace, which need not be the same as the namespace of the service. So, in that case, the spring-generated client proxy will work.

I haven't decided yet whether the WebServiceExporter's replacing a type's specified namespace with the web service's target namespace is either a feature or a bug.

Dave

dsmalley
11-10-2007, 01:42 PM
OK, I have spent some time investigating, and it turns out I've made a couple mis-statements. However, there is a problem with the way WebServiceProxyFactory works, and I think it is a bug.

First off, if you take a simple type like the above MyObject, it is indeed xml serializable by default, and all the specified attributes are unnecessary. Remove them all, and you can build a service with WebServicExporter, and a client with WebServicePorxyFactory, and everything will work as expected. In particular, the WSDL generated will be identical to that produced by a "normal" VS 2005 web service (asmx). So far so good.

However, if you add the "XmlRoot" attribute and assign the Namespace property, here is what happens:

The WSDL generated by WebServiceExporter is again identical to that generated by the "normal" VS 2005 web service. The embedded schema for "MyObject" will now have a different target namespace, which is what you would expect.

However, the product of WebServiceProxyFactory will not be able to deserialize "MyObject" anymore, and will give null instead. The only exception is if the specified Namespace in "XmlRoot" is in fact the same namespace as the web service itself, which is what happens if no Namespace property is specified at all.

Now, in "toy" web services like this, it is a limitation you can live with. Just put all your custom types into the same namespace as your service itself, and everything works fine. But this won't work in the real world. That's because the domain (MyObject in this case), is far more likely to be based on a schema defined externally from the web service project (say the UBL schemas, for instance). Those schemas mix namespaces with abaondon; the individual types all have namespaces, and the defined documents often combine multiple namespaces into the same document. In such cases, you MUST force the domain types to have different namespaces, or the WSDL of your service will probably not be acceptable to other B2B services, because they expect documents which conform to the canonical schemas. You can build a service which uses such a domain model with WebServiceExprorter, but in the process of making the domain objects acceptable to the outside world, they become unusable by a spring-generated proxy.

The one way around this is to have your domain objects implement IXmlSerializable. Why? Because you can implement the IXmlSerializable.ReadXml method to ignore namespaces and only use local names for pulling out the values. But that's a hefty work-around.

If you do put different objects in your domain into unique namespaces, a VS 2005 generated proxy will be able to convert the XML returned by the service into its own "proxy types". You can then write conversion routines to map these "proxy types" to your own domain types, but once agian that's a lot of work and should be unecessary.

I have started looking at the code for WebServiceProxyfactory, and in particular the nested SoapHttpClientProxyTypeBuilder class, but I still don't understand how it all works, so am in no position to try and change this behavior.

But I do think the inability of a spring-generated proxy to handle domain types with xml namespaces is a bug, and ought to be fixed. Failing that, this limitation ought to be documented.

Dave

Bruno Baia
11-10-2007, 02:29 PM
Hi,

The problem here is the XmlRootAttribute. This attribute indicates that the class will be serialized as the root element of a XML document and this is not appropriate to be used for domain object in a Web Service, because your object will be serialized as an element of a Soap Message.
XmlType is enough here, and I think you can use XmlRoot for SoapHeader if I'm not wrong.

- Bruno

dsmalley
11-11-2007, 12:10 AM
Well, yes and no. If you only apply XmlType attribute, things work, but the returned XML will not be correct. Remember that when a web method returns a type, that type IS the root of the returned document (which is contained in the Soap message).

Here is an example, where the web service has a namepsace of nowhere.com and the returned SimpleType has a namespace of somewhere.com. First, using the XmTypeAttribute:

<?xml version="1.0" encoding="utf-8"?>
<SimpleObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://nowhere.com/services">
<Name xmlns="http://somewhere.com">Dave</Name>
<Age xmlns="http://somewhere.com">48</Age>
</SimpleObject>

And then, using the XmlRootAttribute:

<?xml version="1.0" encoding="utf-8"?>
<SimpleObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://somewhere.com">
<Name>Dave</Name>
<Age>48</Age>
</SimpleObject>

As you can see, in both instances, the root of the response is the type, and it has the WRONG namespace assigned if you only use XmlTypeAttribute. All the sub-elements have the correct namespace applied, but because of the root, the result when using XmlTypeAttribue will NOT validate against the original schema.

I suppose we could go back and forth about this forever, but I can also refer you to MSDN documentation which explicitly states types to be used in ASP.NET web services should have the XmlRootAttribute applied to them.

http://msdn2.microsoft.com/en-us/library/ms731779.aspx

I don't mean to beat this horse to death, but I think I have a valid point. The behavior of existing VS 2005 web services and proxies is exactly as I have described: You apply XmlRootAttribute to the types, and the proxies can handle the response based on the generated WSDL. I think Spring generated proxies should behave the same way.

Dave

dsmalley
11-11-2007, 08:50 AM
Oh, OK. No mas. I think I now have 23 or so web service projects on my disk, but I finally got the point. I can use XmlTypeAttribute on the type itself. This will produce the first response above, which has the "wrong" namespace on the root element, but is correctly deserialized by the Spring proxy.

If I leave it that way, but apply the following attribute to the web method:

[return: XmlRoot("SimpleObject", Namespace = "http://somewhere.com")]

The returned XML has the "right" namespace on the root element, and the Spring generated proxy can still deserialize it. I noticed you said in another post there is no way currently to apply the return attribute in configuration, but specifying it explicitly on the method in the implementation class it will be copied to the exported service. Is this still the case? (I know it gets copied if set explicitly; I mean is it still impossible to apply the attribute in configuration).

Thanks,

Dave

Bruno Baia
11-12-2007, 06:12 PM
Hi,

I understand your point, I think an XmlElementAttribute is missing in the Spring generated proxy.

But what I don't get it is why the WebServiceProxyFactory work with a WCF service using the basicHttpBinding.
Using DataContract[Namespace="http://mydomain.com"] from the WCF service side and XmlType[Namespace="http://mydomain.com"] from the client side will works like a charm.


I noticed you said in another post there is no way currently to apply the return attribute in configuration, but specifying it explicitly on the method in the implementation class it will be copied to the exported service. Is this still the case? (I know it gets copied if set explicitly; I mean is it still impossible to apply the attribute in configuration).

Yep this is still the case :(


- Bruno

dsmalley
11-12-2007, 08:17 PM
Hi,

I confess I haven't worked with WCF, but in fact the behavior you describe is similar to what I see with plain ASP.NET. If I apply the XmlTypeAttribute to the type, and then use WebServiceExporter on the server and WebServiceProxyFactory on the client, it works fine.

However, I care about the raw Xml returned because it's a web service, and I can't count on Spring or even .NET on the client side. The problem I have with the wrong namespace on the root element of the response is that while apparently the Spring generated proxy can process it, it doesn't match the schema in the WSDL, and so it can and probably will break with other clients.

I have a work-around for now. I use XmlTypeAttribute, and the return attribute on the method implementation to make the "outside world" see what the WSDL schema says should be there. It's a little "magic" to me that this works, since XmlTypeAttribute + return: XmlRootAttribute produces the same response XML as using XMLRootAttribute on the type, but one works with the proxy and one doesn't.

If the problem really is a missing XmlElementAttribute, would it be possible to apply it manually in the proxy configuration through the MemberAttributes property?

Dave

Bruno Baia
11-13-2007, 03:55 AM
Hi,

I've commited a fix in CVS.
Plz give a try to those dlls (http://maruxelo.free.fr/Spring.NET-20071113.zip) or use the next nightly build (http://www.springframework.net/downloads/nightly/) tomorrow (the one after Spring.NET-20071112-2220.zip) and let me know.

- Bruno

dsmalley
11-13-2007, 11:55 PM
Thanks,

I tried with the new DLL's, and the namespaces are working, except I have another problem now. Inside my object, I have a collection of concrete implementations of an abstract base class. I have applied both the XmlIncludeAttribute's and SoapIncludeAttribute's, and indeed this object has been working fine in a VS 2005 proxy, as well as my own test driver using XmlSerializer, but the Spring proxy is throwing the "Type not expected at this time exception". I am still testing to see if there is some other problem though.

Dave

dsmalley
11-14-2007, 07:03 AM
Hi,

OK, I'm an idiot. The problem with the abstract class and its concrete implementations is showing up in my straight XmlSerializer tests, with no spring at all.

Basically, I have a top-level type, ShellSession, which includes a member which is of type ShellConfig. The serialization works fine at the top-level ShellSession (including the ShellConfig member), but is failing if I try and serialize the ShellConfig only. Odd problem, but it has nothing to do with spring. Thanks again for the fix.

Dave