Page 1 of 1

.Net Service References to JADE Web Services

Posted: Tue Mar 01, 2011 10:28 am
by ghosttie
I am working on a Silverlight project in Visual Studio. Apparently because there's a requirement that all Web Service calls in Silverlight applications be asynchronous they've disabled the .Net 2.0 style Web References, so the only option now is the newer Service References. In the past we've always used Web References with Jade Web Services because they're easy and they just work. Now for the first time we're forced to use Service References, and this is what I've discovered:

Web Service options

If I add a Service Reference that has RPC/encoded selected in JADE, it doesn't create any proxy methods or objects.

It also doesn't like SOAP 1.2 - if I add a service reference with SOAP 1.2 selected, I get this error:
Custom tool warning: Endpoint 'PMAPIServiceSoap' at address 'http://deepthought/pmapi55/jadehttp.dll ... Name=PMAPI' is not compatible with Silverlight 4. Skipping...
Document/Literal seems to require a very verbose notation for parameters where each method only has one parameter, which is an object - you have to create the object and set the parameter values as properties of this object. Both RPC/Literal and Bare use the more normal notation where you can set the parameter values is if it was a normal method call.

I ended up using Bare, because with RPC/Literal I get this warning:
Custom tool warning: Style Document on header sessionId does not match expected style Rpc.
It seems to be OK to use session handling, though I haven't got as far as testing that yet.

Using the WSDL URL of the Web Service is more convenient than exporting and import a WSDL file, because you can just right click on the Service Reference and click Update. However about 2/3 of the time I do this JADE replies with:
The application is too busy. This request cannot be processed at this time due to heavy usage - please try again shortly.
This doesn't cause any real problem, it just requires hitting Update a few times before it works.

Fault handling

When JADE has an exception in a Web Services method, it sends that to the client as a SOAP Fault. We're even throwing certain exceptions intentionally in our Web Service to communicate some types of information with the client.

Apparently as a security measure, the browser doesn't allow Silverlight access to the contents of messages that don't have a HTTP result code of 200 (success). SOAP Faults use a HTTP failure result code (404, 500 etc.), so instead of a FaultException which contains information from the server about what the error was, we get a generic CommunicationException that doesn't contain any information from the SOAP fault.

The solution could be to modify the Web Service to always use code 200 (which would be incorrect according to the SOAP spec but would at least work), or to modify the client to accept messages with failure codes. Since I can't modify JADE, I modified the client using the instructions found here. You just put this code in your Silverlight app before you use Web Services:

Code: Select all

WebRequest.RegisterPrefix("http://", Net.Browser.WebRequestCreator.ClientHttp)
This tells Silverlight to use an alternative HTTP stack, which doesn't restrict access to the content of messages with failure result codes. The only down side is that since we're no longer using the browser's HTTP stack, there could be problems communicating with secure services.

However, even with this fix, the Visual Studio debugger still breaks when a FaultException is thrown. You can't catch the exception with an exception handler because it's thrown in System.ServiceModel, not in your code. It turns out that you can basically ignore this - just telling the debugger to continue allows your app to continue running as if nothing happened and you can access the FaultException in e.Error in your completed event handler. Of course having to hit continue every time your server sends an expected or handled SOAP fault would be annoying, so there's a workaround for it - you tell the debugger to not break on FaultExceptions:
  • Debug menu
  • Exception
  • Common Language Runtime Exceptions
  • System.ServiceModel
  • Uncheck FaultException and FaultException'1
The next annoying thing is that although SOAP faults can contain several pieces of information, FaulExceptions only expose a message to you. As a JADE developer, the errorCode of an Exception is very important, and parsing it out of a message just feels tacky. The only way to get at the full content of the SOAP fault seems to be to get the XML of the message:

Code: Select all

Private Sub loginComplete(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs) If Not e.Error Is Nothing AndAlso TypeOf e.Error Is FaultException Then Dim fe As FaultException = DirectCast(e.Error, FaultException) Dim mf As System.ServiceModel.Channels.MessageFault = fe.CreateMessageFault If mf.HasDetail Then Dim reader As XmlDictionaryReader = mf.GetReaderAtDetailContents Dim iErrorCode As Integer, sErrorItem As String, sErrorText As String While reader.Read() If reader.IsStartElement("errorCode") Then iErrorCode = reader.ReadElementContentAsInt ElseIf reader.IsStartElement("errorItem") Then sErrorItem = reader.ReadElementContentAsString ElseIf reader.IsStartElement("errorText") Then sErrorText = reader.ReadElementContentAsString End If End While ' check iErrorCode and do something appropriate End If End If End Sub

Re: .Net Service References to JADE Web Services

Posted: Thu Mar 03, 2011 9:28 am
by ghosttie
Having actually got to the stage of testing session handling, it turned out that even though there were no errors or warnings when importing the Service Reference, sessions don't actually work. Apparently they're just not supported in Silverlight.

The only way to do this appears to be to use a Message Inspector to get the sessionId from incoming message headers and add it to outgoing message headers.

First of all you need to create a subclass of BasicHttpBinding that uses your subclass of ChannelBase to apply your subclass of IClientMessageInspector to the messages. This would have been a nightmare, except that there's an extremely good example here. It's written in C#, so I had to convert it to VB.Net, but that's wasn't a big deal and maybe you're working in C# anyway. It's all very generic, but there are multiple classes so it's tidy to keep them in a separate class library project.

One you've got all those generic subclasses in your solution, all that you really need to do is to create your own subclass of IClientMessageInspector and tell your client to use it.

The only tricky thing with the message inspector is that you you can't do what you want without messing around with the XML. You can't just stick the sessionId in the header, because it belongs in a <sessionId> tag inside the header. The way to handle this seems to be to create a subclass of XmlObjectSerializer and pass that along with your sessionId to CreateHeader so it can output the tags.

For incoming messages, you can't get the sessionId out of the header without having to use an XmlDictionaryReader, same as we did with FaultExceptions.

Code: Select all

Imports System.ServiceModel Imports System.ServiceModel.Channels Imports System.Xml Imports System.Runtime.Serialization Imports MyAPIMessageInspector Public Class SessionMessageInspector Implements IClientMessageInspector Private pSessionId As String Private pSS As New SessionSerializer() Public Function BeforeSendRequest(ByRef request As Message, ByVal channel As IClientChannel) As Object Implements PMAPIMessageInspector.IClientMessageInspector.BeforeSendRequest If pSessionId <> vbNullString Then request.Headers.Add(MessageHeader.CreateHeader("JadeSessionHeader", "urn:JadeWebServices/PMAPI/", pSessionId, pSS)) End If Return Nothing End Function Public Sub AfterReceiveReply(ByRef reply As Message, ByVal correlationState As Object) Implements PMAPIMessageInspector.IClientMessageInspector.AfterReceiveReply Dim iHdr As Integer = reply.Headers.FindHeader("JadeSessionHeader", "urn:JadeWebServices/PMAPI/") If iHdr >= 0 Then Dim reader As XmlDictionaryReader = reply.Headers.GetReaderAtHeader(iHdr) While reader.Read() If reader.IsStartElement("sessionId") Then pSessionId = reader.ReadElementContentAsString End If End While End If End Sub End Class Friend Class SessionSerializer Inherits XmlObjectSerializer #Region "Do-nothing methods because this is write-only and very simple" Public Overloads Overrides Function IsStartObject(ByVal reader As System.Xml.XmlDictionaryReader) As Boolean Return False End Function Public Overloads Overrides Function ReadObject(ByVal reader As System.Xml.XmlDictionaryReader, ByVal verifyObjectName As Boolean) As Object Return Nothing End Function Public Overloads Overrides Sub WriteStartObject(ByVal writer As System.Xml.XmlDictionaryWriter, ByVal graph As Object) End Sub Public Overloads Overrides Sub WriteEndObject(ByVal writer As System.Xml.XmlDictionaryWriter) End Sub #End Region Public Overloads Overrides Sub WriteObjectContent(ByVal writer As System.Xml.XmlDictionaryWriter, ByVal graph As Object) writer.WriteElementString("sessionId", CStr(graph)) End Sub End Class
To tell your client to use your message inspector, create an instance of your message inspector, use that in the constructor of your subclass of binding, and pass that binding to a constructor of your client that takes a binding and an endpoint. You could hard code the endpoint to a URL or get it from the ClientConfig using a Channel.

Code: Select all

api = New MyAPI.MyAPIServiceSoapClient(New BasicHttpMessageInspectorBinding(New SessionMessageInspector), New ChannelFactory(Of MyAPI.MyAPIServiceSoapChannel)("MyAPIServiceSoap").Endpoint.Address)

Re: .Net Service References to JADE Web Services

Posted: Thu Mar 10, 2011 7:27 am
by ghosttie
Apparently the reason I was getting
The application is too busy. This request cannot be processed at this time due to heavy usage - please try again shortly.
when updating the Service Reference was because I hadn't set MaxInUse in jadehttp.ini

Re: .Net Service References to JADE Web Services

Posted: Wed Aug 31, 2011 6:49 am
by ghosttie
In addition to using

Code: Select all

WebRequest.RegisterPrefix
at the Silverlight end to allow you access to the body of error message, you might also need to change the settings at the server end, or the error body won't be sent in the first place.

You need to disable custom errors in IIS:
  • Select the virtual directory
  • Open Error Pages
  • Click Edit Feature Settings
  • Select Detailed errors
  • Click OK
screenshot.jpg
screenshot.jpg (42.71 KiB) Viewed 5449 times