First off, I found it extremely helpful to use WireShark to analyze my network traffic. This finally gave me some specific information as to the XML I was sending and what I was getting back. I'd recommend it.
I found a set of C# classes that would do all the basics of XMLRPC over HTTP. I'm a VB.NET programmer by habit so this isn't my native language, but I haven't had to even touch this code, it works so well. You can get it at www.xml-rpc.net.
So, let's get into this. Using the basic functionality of XML over RPC, I originally came up with this bit of code that seemed to be sending data, but was constantly getting Internal Server Error (500) responses from the server.
Imports CookComputing.XmlRpc
Public Interface iGetClaim
<CookComputing.XmlRpc.XmlRpcMethod("api.server.clients.claims.search_for_client_claims")> _
Function SearchForClientClaims(ByVal api_id As String, ByVal api_password As String, ByVal client_id As String)
End Interface
Module Module1
Sub Main()
CallRPC()
End Sub
Sub CallRPC()
Dim Proxy As iGetClaim
Dim Protocol As XmlRpcClientProtocol
Dim XmlRpcHost As String = "http://api.xxx.com/rpc"
Dim ApiId As String = "aaa"
Dim Password As String = "bbb"
Dim ClientID As String = "ccc"
'Prepare the request
Proxy = CType(XmlRpcProxyGen.Create(Of iGetClaim)(), iGetClaim)
Protocol = CType(Proxy, XmlRpcClientProtocol)
Protocol.Url = XmlRpcHost
'Get the response
Dim result As String = Proxy.SearchForClientClaims(ApiId, Password, ClientID)
MsgBox(result)
End Sub
End Module
Once I got ahold of WireShark, I was able to analyze what was going out my network "door" and emailed the vendor the exact XML I was sending him:
<?xml version="1.0"?>
<methodCall>
<methodName>api.server.clients.claims.search_for_client_claims</methodName>
<params>
<param>
<value>
<string>aaa</string>
</value>
</param>
<param>
<value>
<string>bbb</string>
</value>
</param>
<param>
<value>
<string>ccc</string>
</value>
</param>
</params>
</methodCall>
This is when we started figuring it out. That's not what they were looking for. What they were looking for was something like this:
<?xml version="1.0"?>
<methodCall>
<methodName>api.server.clients.claims.search_for_client_claims</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>api_id</name>
<value>
<string>aaa</string>
</value>
</member>
<member>
<name>api_password</name>
<value>
<string>bbb</string>
</value>
</member>
<member>
<name>client_id</name>
<value>
<string>ccc</string>
</value>
</member>
<member>
<name>paging</name>
<value>
<struct>
<member>
<name>offset</name>
<value>
<i4>0</i4>
</value>
</member>
<member>
<name>rows</name>
<value>
<i4>2</i4>
</value>
</member>
</struct>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>
Ok... How in the world do we fix that? I almost resorted to tossing the XMLRPC software and writing my own HTTP request and response handler and manually parsing the XML. I could have done that... but really, really, didn't want to. After much googling and a little hair pulling, we figured out how to structure the requests (and later the response) as a heirarchy of VB.NET Classes - XMLRPC code did the rest! Here's what a request winds up looking like in VB:
Imports CookComputing.XmlRpc
Public Interface iClaimInfo
<CookComputing.XmlRpc.XmlRpcMethod("api.server.clients.claims.get_claim")> _
Function GetClaim(ByVal value As Request) As Response
Class Request
Public api_id As String
Public api_password As String
Public claim_id As String
Public claim_number As String
End Class
Class Response
Public claim As Response_ClaimInfo
End Class
Class Response_ClaimInfo
Implements iClaimsFormsObject
Public Sub AddChildNodes(ByRef MyNode As System.Windows.Forms.TreeNode) Implements iClaimsFormsObject.AddChildNodes
'Do Nothing
End Sub
Public Sub ShowInPanel(ByRef Panel As System.Windows.Forms.Panel) Implements iClaimsFormsObject.ShowInPanel
Dim lbl As New Windows.Forms.Label
lbl.Text = XmlRpcInfo.GetProperties(Me)
lbl.Dock = Windows.Forms.DockStyle.Fill
Panel.Controls.Add(lbl)
End Sub
Public Sub DoubleClickNode(ByRef MyNode As System.Windows.Forms.TreeNode) Implements iClaimsFormsObject.DoubleClickNode
MsgBox(XmlRpcInfo.GetProperties(Me))
End Sub
Public client_id As String
Public client_parent_id As String
Public claim_status_description As String
Public claim_number As Integer
Public claim_status_key As String
Public claim_type_name As String
Public claim_created As String
Public adjuster As String
Public client_name As String
Public claim_location_of_loss_city As String
Public claim_date_of_loss As String
Public claim_client_claim_number As String
Public claim_id As String
Public adjuster_id As String
Public claim_location_of_loss As String
Public claim_received As String
Public last_modified As String
Public claim_priority As Integer
Public claim_completed As String
Public claim_type_id As String
Public client_parent_name As String
Public claim_last_worked As String
Public client_display_name As String
Public Function GetAmounts() As iClaimAmounts.Response
Dim a As New ClaimAmounts(claim_id)
Return a.GetAmounts()
End Function
Public Function GetPayments() As iClaimPayments.Response
Dim x As New ClaimPayments(claim_id)
Return x.GetData()
End Function
Public Function GetJournalEntries() As iClaimJournalEntries.Response_JournalEntry
Dim x As New ClaimJournalEntries(claim_id)
Return x.GetData()
End Function
End Class
End Interface
Public Class ClaimInfo
Private _ClaimId As String
Private _ClaimNumber As String
Public Sub New(ByVal ClaimId As String, ByVal ClaimNumber As String)
_ClaimId = ClaimId
_ClaimNumber = ClaimNumber
End Sub
Public Function GetClaim() As iClaimInfo.Response_ClaimInfo
Dim Proxy As iClaimInfo
Dim Protocol As XmlRpcClientProtocol
Dim Response As iClaimInfo.Response
Dim Request As iClaimInfo.Request
'Prepare the request
Proxy = CType(XmlRpcProxyGen.Create(Of iClaimInfo)(), iClaimInfo)
Protocol = CType(Proxy, XmlRpcClientProtocol)
Protocol.Url = XmlRpcInfo.XmlRpcHost
Try
'Send the Request
Request = New iClaimInfo.Request
Request.api_id = XmlRpcInfo.ApiId
Request.api_password = XmlRpcInfo.ApiPassword
Request.claim_id = _ClaimId
Request.claim_number = _ClaimNumber
'Receive the Response
Response = Proxy.GetClaim(Request)
'Return the claim
Return Response.claim
Catch ex As Exception
'Record any errors
MsgBox(ex.ToString)
Return Nothing
End Try
End Function
End Class
Here's an example of the returned XML that these classes can process:
<?xml version="1.0" encoding="us-ascii"?>
<methodResponse>
<params>
<param>
<value>
<struct>
<member>
<name>claim_list</name>
<value>
<array>
<data>
<value>
<struct>
<member>
<name>claim_status_key</name>
<value>
<string>C8</string>
</value>
</member>
<member>
<name>claim_id</name>
<value>
<string>aaa</string>
</value>
</member>
<member>
<name>employee_name</name>
<value>
<string>first last</string>
</value>
</member>
<member>
<name>employee_id</name>
<value>
<string>89DC932C-DE87-3BDC-9219-186A08A7C3E3</string>
</value>
</member>
<member>
<name>claim_number</name>
<value>
<int>xxx</int>
</value>
</member>
<member>
<name>claim_client_claim_number</name>
<value>
<string>xxx</string>
</value>
</member>
<member>
<name>claim_date_of_loss</name>
<value>
<string>03/31/2007</string>
</value>
</member>
<member>
<name>claim_insured</name>
<value>
<string>xxx xxx</string>
</value>
</member>
</struct>
</value>
<value>
<struct>
<member>
<name>claim_status_key</name>
<value>
<string>CD</string>
</value>
</member>
<member>
<name>claim_id</name>
<value>
<string>B3ADD56D-DD4D-3F9F-9D50-212489623927</string>
</value>
</member>
<member>
<name>employee_name</name>
<value>
<string>xxx xxx</string>
</value>
</member>
<member>
<name>employee_id</name>
<value>
<string>91ECC175-2074-3C24-B834-6A20CF807B58</string>
</value>
</member>
<member>
<name>claim_number</name>
<value>
<int>xxx</int>
</value>
</member>
<member>
<name>claim_client_claim_number</name>
<value>
<string>xxx</string>
</value>
</member>
<member>
<name>claim_date_of_loss</name>
<value>
<string>04/01/2007</string>
</value>
</member>
<member>
<name>claim_insured</name>
<value>
<string>xxx xxx</string>
</value>
</member>
</struct>
</value>
</data>
</array>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodResponse>
I've found that I had to kind of "guess" as to whether or not I would get back integers, strings, doubles.... the program would break if I got the wrong type, so I'm still tweaking that. I also developed a few "helper" functions to ease in the writing of code and identifying properties of the returned XML. I'll post a few here:
Public Class XmlRpcInfo
Public Shared XmlRpcHost As String
Public Shared ApiId As String
Public Shared ApiPassword As String
Public Shared ClientId As String
Shared Function GetKeys(ByVal coll As Object, Optional ByVal IncludeDims As Boolean = False) As String
Dim de As Object
Dim str As String = ""
Dim xml As XmlRpcStruct = coll(0)
For Each de In xml.Keys
If IncludeDims = True Then
str += " Public " & de & " as string" & vbCrLf
Else
str += de & vbCrLf
End If
Next
Return str
End Function
All in all, this is turning into one powerful XML over HTTP processor. It looks like I'm going to be able to use it not only for one client but for seveal other around the country, and I'm excited that I finally figured out the basics of how it works. If you have any questions or thoughts, please comment; I'd love to learn more.
0 comments:
Post a Comment