Manually calling a web service with a SOAP message - wcf

I'm trying to call a SOAP-based WCF web service by creating an HTTP request by hand. I need to do this because I'm trying to implement this in an environment that will have HTTP access, but not the WCF service client stuff. It will actually be implemented in a different language, but I'm trying to do a proof of concept in C# first.
The WCF service has a simple function that takes a string address and then returns a complex object with geocoding information about the address. Right now, I'm just looking for it to return a proper response as a string. As it is, it returns HTML describing the WSDL discovery, so the call isn't working.
I pulled the SOAP message from an actual functioning service call (I wrote some code to intercept and extract the SOAP message before it went out).
So the trick now is just getting the rest of the HTTP to work. So my code is as follows:
string soap = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
" <s:Header>" +
" <Action s:mustUnderstand=\"1\" xmlns=\"http://schemas.microsoft.com/ws/2005/05/addressing/none\">http://tempuri.org/IGeoCode/GetLocation</Action>" +
" </s:Header>" +
" <s:Body>" +
" <GetLocation xmlns=\"http://tempuri.org/\">" +
" <address>1600 Pennsylvania Ave NW, Washington, DC 20500</address>" +
" </GetLocation>" +
" </s:Body>" +
" </s:Envelope>";
var obj = new XMLHTTP60();
obj.open("POST", #"http://MyServer:4444/GeoCode.svc");
obj.setRequestHeader("Content-Type", "text/xml");
obj.setRequestHeader("SOAPAction", "http://MyServer:4444/GeoCode.svc");
obj.setRequestHeader("Content-Length", soap.Length.ToString());
obj.send(soap);
string stat = obj.statusText;
string str = obj.responseText;
string resp = obj.getAllResponseHeaders();
What I expect back is something along the lines of:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<GetLocationResponse xmlns="http://tempuri.org/">
<GetLocationResult xmlns:a="http://schemas.datacontract.org/2004/07/GeoCodeSvc" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
...
Lots of stuff here
...
</GetLocationResult>
</GetLocationResponse>
</s:Envelope>
</s:Body>
Instead, I'm getting a 400 (Bad Request) status.

I think your problem is with the SOAPAction statement. You don't want to point that to your service. Instead, it should be whatever the WSDL calls the end point.
If you're feeling adventurous, the end point is typically one of the last entries in the WSDL. I can't recall the specific term they use, but it should be obvious.
HTH, Jim

Related

CalDav sync-token expiry

In a previous question, I asked about syncing data with a DaviCal server. I ended up concluding that there was a bug on DaviCal as when querying a CalDav server with an invalid sync token, the server should return an error but, instead, it returns the whole set of events on the server.
So I started looking for an alternative CalDav server. I use SabreDav. I followed their very handy tutorial here.
On there it says:
Caveats
Note that a server is free to 'forget' any sync-tokens that have been previously issued. In this case it may be needed to do a full-sync again.
In case the supplied sync-token is not recognized by the server, a HTTP error is emitted. SabreDAV emits a 403.
That looks promising : exactly what I need !
So what I do is getting my sync-token which is http://sabre.io/ns/sync/15.
I then submit my REPORT query as follows (maybe that's where I do things wrong?!)
string syncToken = "http://sabre.io/ns/sync/15";
string body = " <d:sync-collection xmlns:d=\"DAV:\"> " +
" <d:sync-token>" + syncToken + "</d:sync-token> " +
" <d:sync-level>1</d:sync-level> " +
" <d:prop> " +
" <d:getetag/> " +
" </d:prop> " +
" </d:sync-collection> ";
Request = (HttpWebRequest)HttpWebRequest.Create("http://my.sabredav.com/calendars/example/home/");
Request.Credentials = new NetworkCredential("my_user", "my_pwd");
Request.Method = "REPORT";
Request.ContentType = "application/xml";
// set the body of the request...
Request.ContentLength = body.Length;
using (Stream reqStream = Request.GetRequestStream()) {
// Write the string to the destination as a text file.
byte[] encodedBody = Encoding.UTF8.GetBytes(body);
reqStream.Write(encodedBody, 0, encodedBody.Length);
reqStream.Close();
}
// Send the method request and get the response from the server.
Response = (HttpWebResponse)Request.GetResponse();
So when I send the request using the valid sync-token http://sabre.io/ns/sync/15 I just got, I get an empty response:
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.or /ns/">
<d:sync-token>http://sabre.io/ns/sync/15</d:sync-token>
</d:multistatus>
So far so good.
If I use a previous sync-token that I know was valid at some point (http://sabre.io/ns/sync/14 in this case - by chance it is the previous number...) I get changes as expected:
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">
<d:response>
<d:href>/calendarserver.php/calendars/admin/default/23b351ee-7677-46e3-b5c5-5263e8fca351.ics</d:href>
<d:propstat>
<d:prop>
<d:getetag>"8d8d122a66625ca990252fae652cd2e5"</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<d:sync-token>http://sabre.io/ns/sync/15</d:sync-token>
</d:multistatus>
But the issue is, that if I use a RANDOM sync token that I definitely know has never been issued: http://sabre.io/ns/sync/1234312231344324
I get the SAME answer as when I was using the latest (current) token:
<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">
<d:sync-token>http://sabre.io/ns/sync/15</d:sync-token>
</d:multistatus>
SabreDav says it's supposed to throw an error 403, but clearly, it's not what I get here... However, if I use INVALID sync-token format, say token1234, I do indeed get an error 403...
So the question is: HOW can I make sure my sync-token is still valid ? So, if the token is valid and nothing is returned, I know my local cache is up to date or, in case the sync-token is INVALID, I need to do full sync!
This is indeed a bug in sabre/dav. Feel free to open a bug report so it's on our radar. It's never been a major concern, as 'well behaved clients' would never supply a sync-token that was never issued by us.
The reason it's happening, is because the digit is simply an ever increasing record id in our database. To fetch all the changes a simple sql query with a > is done.
If you explicitly want to test invalid tokens in your client, I would recommend to simply supply a sync-token with a completely different format (something that doesn't start with http://sabre.io/ns/sync).

Web Service that converts address to lat/Long

I was wondering if anyone knows of a good web service that I can supply an address and it returns a Lat/Long in decimal degrees? I have been trying to play with the Google Maps API, but I cannot find the documentation for using a desktop app(Winforms) to return the data. I have used both WebClient and the WebRequest/WebResponse ways without success. The error is '610:Invalid key' though I am staring at the key and just copy/pasted it into the parameters - including making a new one. It seems Google is not an easy route and I would like a diff option if poss. Any ideas would be great. Thanks for looking.
You're looking for Google Geocoding API v3
There is no separate things for Desktop application. So you're looking exactly for this
"https://maps.googleapis.com/maps/api/geocode/xml?address=
your encodedAddress&sensor=false"
Here is a simple example I used in C#:
string address = "your address";
encodedAddress = HttpUtility.UrlEncode(address).Replace("+", "%20");
string googleApiURL = "https://maps.googleapis.com/maps/api/geocode/" + "xml?address=" + encodedAddress + "&sensor=false";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(googleApiURL);
request.Timeout = 10000;
request.Method = "GET";
WebResponse response = request.GetResponse();
if (response != null)
{
//Parse here to to capture the lat and long
}
else
{
//Handle if don't get response
}
For you, it won't be difficult to convert it to VB.NET.
The major part here is how you are going to parse the XML/JSON response, As I already bold faced in URL
Google provide two different response xml or json.
Don't forget to encode the address.
NOTE: Use of the Google Geocoding API is subject to a query limit of 2,500 requests per day.
Hope you got some idea.

WCF Service ref string param that accepts XML?

I'm writing a WCF service that receives events. It's to an agreed standard so I've got to stick to the service definition, and I don't control the data the clients send. Again this is to an agreed standard although the data can vary.
Here's one of the methods on my service:
complexType ErrorEvent(int requestId, complexType returnValue, ref string errorInfo)
Clients send XML in the errorInfo string that my function will manipulate and return.
The data I get is like this (full SOAP request):
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ErrorEvent xmlns="http://blah">
<requestId>1</requestId>
<returnValue>
<returnCode>0</returnCode>
</returnValue>
<errorInfo>
<ErrorMessage>An error message</ErrorMessage>
<DefaultTask><!-- Complex data --></DefaultTask>
<Task><!-- Complex data --></Task>
<Task><!-- Complex data --></Task>
<Task><!-- Complex data --></Task>
<ExtraMessage>hello</ExtraMessage>
<ExtraMessage>world</ExtraMessage>
</errorInfo>
</ErrorEvent>
</s:Body>
</s:Envelope>
However, when I try and run this I get this error (edited):
The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter errorInfo. The InnerException message was 'There was an error deserializing the object of type System.String. End element 'errorInfo' from namespace '' expected. Found element 'ErrorMessage' from namespace ''.
So my question is, is there any way I achieve what I want to do without altering the signature of my method? For example adding attributes to my service etc? Or do I need to intercept the message?
Thanks for any pointers.
Does it have to be passed as a string? You know you can also receive XmlElement and XElements in WCF?
Are you generating the SOAP request yourself? Could you use a CDATA section, i.e.
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ErrorEvent xmlns="http://blah">
<requestId>1</requestId>
<returnValue>
<returnCode>0</returnCode>
</returnValue>
<errorInfo>
<![CDATA[<ErrorMessage>An error message</ErrorMessage>
<DefaultTask><!-- Complex data --></DefaultTask>
<Task><!-- Complex data --></Task>
<Task><!-- Complex data --></Task>
<Task><!-- Complex data --></Task>
<ExtraMessage>hello</ExtraMessage>
<ExtraMessage>world</ExtraMessage>]]>
</errorInfo>
</ErrorEvent>
</s:Body>
</s:Envelope>
I didn't find a way to make this work and as #luksan says, it's a misuse by a client to send unescaped XML to a string parameter.
The workaround I adopted was to make a class that implements IClientMessageInspector, IDispatchMessageInspector and IEndpointBehavior to intercept, check and modify any messages that were incorrect.
If I could have changed the interface, another workaround would have been to accept XmlNode[] instead of string.

Problems with Delphi Rest Client talking to C# Rest server

I am writing a REST WCF Service, and have it working for connections from a C# client, but am having issues with connecting via a Delphi 2009 client. The problem I have is specifically with a PUT request, which looks (for the moment) as below. It expects an XML request, containing the document's objects.
[WebInvoke(UriTemplate = "Document/{id}", Method = "PUT", RequestFormat=WebMessageFormat.Xml)]
public void UpdateDocument (string id, Document document)
{
Document doc = document;
// this should update or something!
Console.WriteLine(doc.Id);
}
When I try and call this via my Delphi client (as shown below), I get a 'Bad Request'. Oddly if I send an empty document, the request is received, but obviously has no data.
...
msg := '<?xml version="1.0"?>' +
'<Document>' +
'<Id>123456788888</Id>' +
'</Document>';
XMLDocument1.LoadFromXML(msg);
xmlStream := TMemoryStream.Create;
idHttp1.Request.ContentType := 'application/xml';
XMLDocument1.SaveToStream(xmlStream);
url := 'http://localhost:50435/service1/Document/12345678';
result := idHttp1.Put(url, xmlStream);
ShowMessage (result);
...
Any ideas, as I am a bit lost now!
Thanks
The mapping of the URI to server method is not correct. According to default mapping a PUT request will invoke AcceptDocument method, and a POST request will invoke UpdateDocument method.
http://docwiki.embarcadero.com/RADStudio/en/REST
Also passing a TStream as parameter is also possibility for difficulties. Is it possible for you to use JSON? I am not sure Delphi 2009 has support for JSON though.
Edit:
quote from Delphi documentation:
by default, a prefix of 'update' is assigned to any method invoked
with POST. Similarly, a prefix of 'cancel' is used for DELETE
requests, and a prefix of 'accept' is used for PUT requests. This
prefixing can be avoided by putting quotation marks around the method
name
Above quote applies to Delphi. Maybe also for WCF.
As per my comment above, installing Fiddler made me look at the actual page that the webservice outputs, this helpfully has example XML and json structures. I was missing the namespace for the object, so I guess the WCF end didn't know what to translate, changing the message to the following made it work:
msg := '<?xml version="1.0"?>' +
'<Document xmlns="http://schemas.datacontract.org/2004/07/Contracts.Contracts">' +
'<Id>123456788888</Id>' +
'</Document>';

Accessing the HTTP headers from a WCF Service

I need to access the HTTP response headers that are to be returned to the client from a WCF Service. Accessing the HTTPContext is easy(through HttpContext.Current.Response), but what is the event/extension/behavior that is executed lastly, when the StatusCode is already set (for ex. if the status is 500)?
EDIT: Message Inspectors don't seem to be a good solution here, because at the time they run, the status code isn't set yet. (At least in my trial that was the case)
You can access all headers on WebOperationContext.Current.IncomingRequest, like this:
IncomingWebRequestContext request = WebOperationContext.Current.IncomingRequest;
WebHeaderCollection headers = request.Headers;
Console.WriteLine("-------------------------------------------------------");
foreach (string headerName in headers.AllKeys)
{
Console.WriteLine(headerName + ": " + headers[headerName]);
}
Console.WriteLine("-------------------------------------------------------");
See here
Simplest way for having control on the Headers is to use Message contracts.
Use Message Inspectors to monitor the message right after receiving it at the Service end.
In an extreme case, where you are not satisfied with any other standard routes, you can go for POX (Plain Old XML) type operations where you would be dealing with raw XML message.