How to handle URL-encoded POST request with self-hosted WCF? - c#

I have self-hosted WCF service. It works as charm, when the input data format is known, but it's not my case.
My service would be invoked like a HTML form would be posted to it, but I don't know exact list of parameters which would be sent.
External web service would send a HTTP request with content "a=foo&b=bar", yes, URL-encoded. Parameters in content may vary. Their count may vary. Some parameters may or may not be present. So I'm perfectly fine with whole URL-encoded string, I'll decode it myself. I have to reply with ASCII string "OK".
How to access this "a=foo&b=bar" text from the request? Can I create my own encoder? How it's done?
Here's what I started with, the contract:
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "test", BodyStyle = WebMessageBodyStyle.Bare)]
Stream TestPostRequest();
And here's the method:
public Stream TestPostRequest() {
var maxByteSize = 16384;
var messageStream = new MemoryStream(maxByteSize);
var messageBuffer = OperationContext.Current.RequestContext.RequestMessage.CreateBufferedCopy(maxByteSize);
messageBuffer.WriteMessage(messageStream);
var rawRequestData = new byte[maxByteSize];
messageStream.Read(rawRequestData, 0, maxByteSize);
var requestDataString = Encoding.UTF8.GetString(rawRequestData);
Console.WriteLine(requestDataString);
var responseData = new byte[2] { 79, 75 }; // OK
var responseStream = new MemoryStream(responseData);
return responseStream;
}
Obviously it doesn't work, because CreateBufferedCopy method expects XML as input data, but gets URL-encoded string. As all the other methods I found.
I'm not afraid of creating new class implementing IEndpointBehavior or IDispatchMessageInspector, but I just don't see where I could implement something allowing me to access the request data raw, as bytes, not XML.
Maybe there is a point where I could convert raw data to XML?
EDIT:
My fault, there was a broken behavior in my operation stack which messed with message content type badly. The moral is - test new features isolated, in a clean test project. The second moral is WCF can everything ;) And the third is: if there is no XML in message internal data - you broke it ;)

Related

Reading XML content from Body of WebAPI call is cut off at beginning

I'm creating a sort of proxy service that needs to process calls containing XML from the body of a POST to my WebAPI service and then POST it on to another service.
The odd thing is when I receive the XML message from the POST the 1st part of the XML from the body is cut off. Initially I thought maybe buffer size, or message was too big so I cut out a lot of the XML test message being sent reducing what was being sent. However the XML is still cut off in the same place.
I've tried the following (2) methods to read the XML BODY in the WebAPI service and the result is the same:
var reader = new StreamReader(Request.Content.ReadAsStreamAsync().Result);
string originalMessage = reader.ReadToEnd();
and:
var result = "";
Request.Content.ReadAsStreamAsync().ContinueWith(x =>
{
using (var sr = new StreamReader(x.Result))
{
result = sr.ReadToEnd();
}
});
Here is a snippet the original XML:
<Message version="123" release="001" xmlns="http://www.mysite.com/schema">
<Header>
<To Att1="A">001</To>
<From Att2="B">002</From>
<ID>9876</ID>
Here is the beginning of the content after reading it in the WebAPI controller POST:
</To>
<From Att2="B">002</From>
<ID>9876</ID>
See how it starts at the 'closing' tag of the <To> element? That's obviously not the beginning of the XML that it was sent.
The even more peculiar thing is the 'Content Size' when inspected before it is sent and after is 4188 on both sides. Something else interesting is that I have an old fashion .asmx tester (as opposed to a Web API service) that does the identical thing. When I read the incoming XML message in that app using the following:
// Get raw request body
Stream receiveStream = HttpContext.Current.Request.InputStream;
// Move to beginning of input stream and read
receiveStream.Position = 0;
using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
{
// Load into XML document
xmlSoapRequest.Load(readStream);
}
...I see the full XML message. So not sure why the .asmx can read it but not the WebAPI service fully.
What am I doing wrong in my WebAPI POST call to where I can't see the full XML message that was sent in the body of the request?
Well I figured out the issue, but not 100% sure on the reason. The parameter I was using for the POST was the one right out of the box:
public HttpResponseMessage Post([FromBody]string value)
I changed it to take the request as a parameter on the POST instead:
public HttpResponseMessage Post(HttpRequestMessage request)
When I did the 2nd option above, I began to get the entire XML body from the request as expected.

WebRequest: Query string data vs x-www-form-urlencoded content

I am trying to call Google's OAuth2 authentication service as per these instructions: https://developers.google.com/accounts/docs/OAuth2ForDevices
I put all of the required parameters into the query string and sent the request. This worked for the "Obtaining a user code" section but not for the "Obtaining Access and Refresh Tokens" section.
After much playing around and getting 400 Bad Request errors, I found that, instead of putting the data in the query string, you can create a request with a FormUrlEncodedContent and send the data through as content with application\x-www-form-urlencoded Content-Type.
Here is the code before:
var requestMessage = new HttpRequestMessage();
requestMessage.Method = "POST";
requestMessage.RequestUri = new Uri(fullUrl);
Where fullUrl is something like:
https://accounts.google.com/o/oauth2/device/code?client_id=812741506391-h38jh0j4fv0ce1krdkiq0hfvt6n5amrf.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile
And the new code is:
var requestMessage = new HttpRequestMessage();
requestMessage.Method = "POST";
requestMessage.RequestUri = new Uri(url);
requestMessage.Content = new FormUrlEncodedContent(CreateDictionary(queryStringNames, queryStringValues));
Where url is:
https://accounts.google.com/o/oauth2/device/code
and queryStringNames and queryStringValues are string arrays of the names and values of the required parameters.
What is the difference between these two methods? Is it safe to assume that all POST calls can use the URL Encoded Content requests instead of putting the data in the query string?
In general, POST requests do not need query string but it is still subjected to Server's logic implementation. In case of OAuth which is quite known standard and they do follow good practice, it is safe to use form encoded data unless mentioned explicitly in API to send Parameter as query string.
Query String & Post data are two different set of parameters. If server is expecting Query string then you must send query string only. It all depends on how server side logic is implemented. You can not use them alternatively. Most API documentation specify clearly what are they expecting.

MemoryStream data corruption issue

I have created a simple REST based WCF service which runs on BasicHttpBinding. In one of my webmethod, I am returning a Stream which points to a JSON response.
The Method looks like :
[OperationContract]
[FaultContract(typeof(ApplicationFault))]
[WebInvoke(Method = "POST", UriTemplate = "GetActiveCalls/{nurseid}")]
Stream GetActiveCalls(string nurseid);
From the body of the GetActiveCalls, I am creating an object of MemoryStream and returning the same as response. The code looks like
// Serialize the results as JSON
string jsonResult = new JavaScriptSerializer().Serialize(baseResponses);
// ContentType json
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
WebOperationContext.Current.OutgoingResponse.Headers.Add("Cache-Control", "no-cache");
var bytes = Encoding.UTF8.GetBytes(jsonResult);
//Parse to memorystream
var ms = new MemoryStream(bytes);
ms.Seek(0, SeekOrigin.Begin);
ms.SetLength(bytes.LongLength);
return ms;
When trying this from client, I get result like
{"LastEvents":[{"FormatValues":"Klic 2 3 4","Icon":null,"Color":"Red","Acknowledged":false,"EventID":28566}],"Message":"","Status":true}
But sometimes after invoking the same method for multiple times, I start getting the response as :
{"LastEvents":[{"FormatValues":"Klic 2 3 4","Icon":null,"Color":"Red","Acknowledged":false,"EventID":28566}],"Message":"","Statu{"LastEv
You can see after "Statu on the JSON response, the stream gets reset and starts getting data from the beginning.
It looks strange to me.
*From server side, when I put breakpoint, it seems the MemoryStream has correct response.
Putting aside the question of using a memory stream or not, I encountered a similar issue just recently, where the memory stream response appeared corrupted, seemingly randomly. The solution to this problem was to remove the tracing sections from web.config, which I had turned on in dev mode. This may or may not be your issue, but it might be worth having a look at. Seems as though this problem is still present in .NET 4.5 as well.

json with C# in a WCF service?

I am looking at adding SMS abilities to my WCF service. I found a cheap SMS service called Penny SMS.
Their interface supports json. But I have no idea how to call it in my WCF service.
Here is the interface/example:
Sample JSON-RPC Request
{ "method": "send",
"params": [
"YOUR_API_KEY",
"msg#mycompany.com",
"5551231234",
"Test Message from PENNY SMS"
]
}
How would I call that with C# from a WCF service? What I am looking for is a way to wrap this into a method call. Something like:
StaticSMSClass.SendSMS("1234567890", "My Message to send");
Note they also support an XML-RPC API if that is more doable from C#.
UPDATE: I made a stab at creating a call myself, but it did not work. I am going to post my attempt in a separate question and see if anyone has a way to do it.
You need to send a HTTP POST with the JSON message to the remote server. You can do this with HttpWebRequest. You either build the JSON manually (the messages seem simple), or define types for it and use a JSON serializer.
MSDN has an example, for your case it would look something like (untested):
string json = // Your JSON message
WebRequest request = WebRequest.Create ("http://api.pennysms.com/jsonrpc");
request.Method = "POST";
var postData = Encoding.UTF8.GetBytes(json);
request.ContentLength = postData.Length;
request.ContentType = "text/json";
using(var reqStream = request.GetRequestStream())
{
reqStream.Write(postData);
}
using(var response = request.GetResponse())
{
// Response status is in response.StatusCode
// Or you can read the response content using response.GetResponseStream();
}
See my answer to the question "Client configuration to consume WCF JSON web service" for how the create a JSON client with WCF.
The answers so far are good, but one additional thing you can take advantage of (since you are within a WCF service) is the use of DataContractJsonSerializer.
In particular, I refer to how you actually populate your JSON in the first line of driis's example.
string json = // Your JSON message
Now, one of the best ways to do this may be to create a new class with these members:
[DataContract]
class SomeType
{
[DataMember]
string method;
[DataMember]
string[] params;
}
Then, just create an instance of SomeType every time and serialize it to JSON using the DataContractJsonSerializer every time you want to send over a piece of data. See http://msdn.microsoft.com/en-us/library/bb412179.aspx for how to use the DataContractJsonSerializer stand-alone.
Hope this helps!
Check the WCF REST API. They serve JSON, maybe they also can send JSON (in intra WCF solution it works). Maybe you have to construct the contract in wsdl to get the service running but maybe it works out.

how to send data from the client to the server using POST?

I am struggling sending data from my rest client to my rest server...
I have created a rest server sending xml to the client, and that works well. However, sending data from the client to the server, I am having a hard time.
Client:
_httpClientRead = new HttpClient("http://127.0.0.1:8000/");
var form = new HttpUrlEncodedForm();
form.Add("startDate", startDate);
_httpClientRead.Post("test", form.CreateHttpContent())
Server:
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "test")]
Meeting CreateNewMeeting(string startDate);
The problem seems to be the HttpUrlEncodedForm on the client side. If I am sending an empty HttpUrlEncodedForm object in the post request, the server receives the request. When adding the HttpUrlEncodedForm attributes, the server never receives the request, and there are no error messages!
What am I missing here? ( the server is returning xml )
How should the post data be sent to the server?
Thanks
I think it is a problem that you use HttpUrlEncodedForm on the client side, while the default on the server side is Xml. To make it clear set the request format on the server side to be RequestFormat = WebMessageFormat.Xml (set this in the WebIncoke attribute). After doing this you can configure your client to send valid xml. Also make sure you use the correct xml namespace. The easiest way to handle this is to use a function that will create the content automagically for you:
var httpContent = HttpContentExtensions.CreateDataContract(objectToSendToServer);
// And then send it using post:
_httpClient.Post("serviceUrl", httpContent);
Note that you also need set the DefaultHeader on the HttpClient to "application/xml".
WCF expects the data to be sent serialized by the DataContractSerializer. You cannot send other media types like application/x-www-form-urlencoded by default.
See this question on how to do it. Best way to support "application/x-www-form-urlencoded" post data with WCF?

Categories

Resources