WCF responds with HTTP 400 to serialized JSON string from Razor Pages app - wcf

I'm trying to POST a request containing JSON from a Razor Pages app to a WCF service endpoint expecting a Json WebMessageFormat and Bare BodyStyle.The JSON passes just fine via Postman, but not when I send it through http-client. Wireshark also shows some extra bytes around JSON in the http-client produced packet that are not present in the Postman packet. Wireshark also reports this as line-based text data: application/json for the Postman packet. The .Net packet is JavaScript Object Notation: application/json.
Here's my C# code to send the JSON to the WCF endpoint:
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:8000");
dynamic foo = new ExpandoObject();
foo.position = 1;
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(foo), System.Text.Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:8000/WCFService/ControllerV1/PostJSON");
request.Headers.Add("cache-control", "no-cache");
request.Headers.Add("Accept", "*/*");
request.Headers.Add("Connection", "keep-alive");
request.Content = content;
try
{
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException e)
{
Console.WriteLine(e.Message);
}
And here's my WCF endpoint declaration:
[OperationContract, WebInvoke(Method="POST", RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
void PostJSON(string jsonString);
I would expect the packets to produce the same response from the server, but, what appears to be the same string produces a response 200 when the packet is built by postman and a response 400 when built by .Net. I'm clearly missing something subtle here, but I can't seem to tease it out.

There are 2 possible BodyStyle for request and response, wrapped or bare. When you specify wrapped body style the WCF service expects a valid json to be passed which in your case would be
//note that property name is case sensitive and must match service parameter name
{
"jsonString": "some value"
}
And when you specify bare format the service expects only plain string value (in case of primitive type as yours) as the request like this
"some value"
When you serialize your object like this
dynamic foo = new ExpandoObject();
foo.position = 1;
string result = JsonConvert.SerializeObject(foo);
the result contains the following json
{
"position":1
}
which corresponds to wrapped format and the service returns 400: Bad Request. All you need to do is to turn this json into valid json string value like this
"{\"position\":1}"
It can be done by repeated JsonConvert.SerializeObject call
dynamic foo = new ExpandoObject();
foo.position = 1;
string wrapped = JsonConvert.SerializeObject(foo);
string bare = JsonConvert.SerializeObject(wrapped);
var content = new StringContent(bare, System.Text.Encoding.UTF8, "application/json");

Related

What's wrong with the API call request

Working on a third part API host. The API call requires a few key/value pairs in the request body with the following example fields:
field1: fieldValue1
field2: fieldValue2
field3: fieldValue3
field4: fieldValue4
The content type should be "application/x-www-form-urlencoded".
When using Postman to make the call, it's always successful. However, when changed to use a simple service to make the call, it's failing. The following is the piece of code that set up the HttpRequestMesage and make the call:
HttpResponseMessage response;
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://soem.thirdpartyAPIHost.net"))
{
request.Content = new StringContent("{\"field1\":\"fieldValue1\",\"field2\":\"fieldValue2\",\"field3\":\"fieldValue3\",\"field4\":\"fieldValue4\"}", Encoding.UTF8, "application/x-www-form-urlencoded");
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");
response = await httpClient.SendAsync(request);
}
}
What's wrong with this implementation?
Found the issue: the content type is Url encoded and therefore the string cannot be in Json format. Instead, the content key/value pairs should be chained together using '&', just like when the parameters are embedded in the Url. So, the sample content in the question should be like this:
request.Content = new StringContent("field1=fieldValue1&field2=fieldValue2&field3=fieldValue3&field4=fieldValue4", Encoding.UTF8, "application/x-www-form-urlencoded");
Please also reference the following: How to send a POST request with Content-Type "application/x-www-form-urlencoded"

RestSharp RestResponse is truncating content to 64 kb

Hi I am using the RestSharp to create the request to my web API. Unfortunately the response.content does not contain full response, which I am able to see when I perform request through browser or fiddler. The content is being truncated to 64 kb. I am attaching my code below.
Could you please advice what could solve this issue?
var request = new RestRequest("Products?productId={productId}&applicationId={applicationId}", Method.GET);
request.RequestFormat = DataFormat.Json;
request.AddParameter("productId", id, ParameterType.UrlSegment);
request.AddParameter("applicationId", Settings.ApplicationId, ParameterType.UrlSegment);
request.AddHeader("X-AppKey", token.AppKey);
request.AddHeader("X-Token", token.Token);
request.AddHeader("X-IsWebApi", "true");
RestResponse response = (RestResponse) client.Execute(request);
if (response.StatusCode == HttpStatusCode.Found)
{
// The following line failes because response.Content is truncated.
ShowProductModel showProductModel =
new JavaScriptSerializer().Deserialize<ShowProductModel>(response.Content);
// Do other things.
return ShowProductApi(showProductModel, q, d, sort, breadcrumb);
}
This is happening because RestSharp uses the HttpWebRequest class from the .NET Framework. This class has a static attribute called DefaultMaximumErrorResponseLength. This attribute determines the max length of an error response, and the default value for this attribute is 64Kb.
You can change the value of that atribbute before instatiating the RestRequest class.
Here's some code:
HttpWebRequest.DefaultMaximumErrorResponseLength = 1048576;
var request = new RestRequest("resource" + "/", Method.POST)
{
RequestFormat = DataFormat.Json,
JsonSerializer = new JsonSerializer()
};
That way your error response can be longer without problemns.
It looks like HttpStatusCode.Found may be causing the issue. That equates to Http Status Code 302 which is a form of redirect. I'm not entirely sure if that's necessarily the right thing to do in this case. If you have "found" the data you are looking for you should return a success level status code, e.g. 200 (Ok). Wikipedia has a list of HTTP Status Codes with summaries about what they mean and links off to lots of other resources.
I've created a little demonstrator solution (You can find it on GitHub) to show the difference. There is a WebApi server application that returns a list of values (Hex codes) and a Console client application that consumes the resources on the WebApi application.
Here is the ValuesFound resource which returns HTTP Status Code 302/Found:
public class ValuesFoundController : ApiController
{
public HttpResponseMessage Get(int count)
{
var result = Request.CreateResponse(HttpStatusCode.Found, Values.GetValues(count));
return result;
}
}
And the same again but returning the correct 200/OK response:
public class ValuesOkController : ApiController
{
public HttpResponseMessage Get(int count)
{
var result = Request.CreateResponse(HttpStatusCode.OK, Values.GetValues(count));
return result;
}
}
On the client side the important part of the code is this:
private static void ProcessRequest(int count, string resource)
{
var client = new RestClient("http://localhost:61038/api/");
var request = new RestRequest(resource+"?count={count}", Method.GET);
request.RequestFormat = DataFormat.Json;
request.AddParameter("count", count, ParameterType.UrlSegment);
RestResponse response = (RestResponse) client.Execute(request);
Console.WriteLine("Status was : {0}", response.StatusCode);
Console.WriteLine("Status code was : {0}", (int) response.StatusCode);
Console.WriteLine("Response.ContentLength is : {0}", response.ContentLength);
Console.WriteLine("Response.Content.Length is: {0}", response.Content.Length);
Console.WriteLine();
}
The count is the number of hex codes to return, and resource is the name of the resource (either ValuesOk or ValuesFound) which map to the controllers above.
The console application asks the user for a number and then shows the length of response for each HTTP Status Code. For low values, say 200, both versions return the same amount of content, but once the response content exceeds 64kb then the "Found" version gets truncated and the "Ok" version does not.
Trying the console application with a value of about 9999 demonstrates this:
How many things do you want returned?
9999
Waiting on the server...
Status was : OK
Status code was : 200
Response.ContentLength is : 109990
Response.Content.Length is: 109990
Status was : Redirect
Status code was : 302
Response.ContentLength is : 109990
Response.Content.Length is: 65536
So, why does RestSharp do this? I've no idea why it truncates content in one instance and not in the other. However, it could be assumed that in a situation where the server has asked the client to redirect to another resource location that content exceeding 64kb is unlikely to be valid.
For example, if you use Fiddler to look at what websites do, the responses in the 300 range (Redirection) such as 302/Found do have a small content payload that simply contain a little HTML so that the user can click the link to manually redirect if the browser did not automatically redirect for them. The real redirect is in the Http "Location" header.

WCF API and API Key authorisation

Written or started to write a WEB API rest service in WCF. It's all going relatively well. However, I've come across a small problem. I've implemented this;
http://blogs.msdn.com/b/rjacobs/archive/2010/06/14/how-to-do-api-key-verification-for-rest-services-in-net-4.aspx
For key validation. (I'm not sure if this is the correct approach for WCF WEB API, since it looks more like the rest service implementation).
Anyway, it seems to work. However, when the api key is not provided the exception is not been displayed in the browser. I.e. if I provide the key, it returns correctly, if I don't it just shows a blank page.
private static void CreateErrorReply(OperationContext operationContext, string key)
{
// The error message is padded so that IE shows the response by default
using (var sr = new StringReader("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + APIErrorHTML))
{
XElement response = XElement.Load(sr);
using (Message reply = Message.CreateMessage(MessageVersion.None, null, response))
{
HttpResponseMessageProperty responseProp = new HttpResponseMessageProperty() { StatusCode = HttpStatusCode.Unauthorized, StatusDescription = String.Format("'{0}' is an invalid API key", key) };
responseProp.Headers[HttpResponseHeader.ContentType] = "text/html";
reply.Properties[HttpResponseMessageProperty.Name] = responseProp;
operationContext.RequestContext.Reply(reply);
// set the request context to null to terminate processing of this request
operationContext.RequestContext = null;
}
}
}
Instead of this showing an error, the result is a blank response. Can anyone help?

How to communicate WCF exceptions to WebClient

I have a WCF web service which throws exceptions when invalid data is submitted. The data is submitted via an HTTP Post using the WebClient object.
Here is the code for the web service:
[WebInvoke(UriTemplate = "update", Method = "POST")]
public JsonValue Update(HttpRequestMessage message)
{
var context = new Entities();
dynamic response = new JsonObject();
// in order to retrieve the submitted data easily, reference the data as a dynamic object
dynamic data = message.Content.ReadAs(typeof(JsonObject), new[] { new FormUrlEncodedMediaTypeFormatter() });
// retrieve the submitted data
int requestId = data.requestId;
int statusId = data.statusId;
string user = data.user;
string encryptedToken = data.token;
string notes = data.notes;
// retrieve the request with a matching Id
var request = context.Requests.Find(requestId);
// make sure the request exists
if (request == null)
throw new FaultException("The supplied requestId does not exist.");
// make sure the submitted encrypted token is valid
var token = DecryptToken(encryptedToken);
if (token == null)
throw new FaultException("Invalid security token.");
// TODO: Validate other token properties (e.g. email)?
if (!request.User.UserName.Equals(token.UserName))
throw new FaultException("Invalid security token.");
// additional logic removed ...
}
And here is the code that submits data to the web service:
// use the WebClient object to submit data to the WCF web service
using (var client = new WebClient())
{
client.Encoding = Encoding.UTF8;
// the data will be submitted in the format of a form submission
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
var data = new NameValueCollection();
// prepare the data to be submitted
data.Add("requestId", requestId.ToString());
data.Add("statusId", this.StatusId);
data.Add("token", token.ToString());
data.Add("user", this.User);
data.Add("notes", this.Notes);
// submit the data to the web service
var response = client.UploadValues(this.Address, data);
}
I keep getting an exception with message: "The remote server returned an error: (500) Internal Server Error" at client.UploadValues(this.Address, data);.
Is there a way I can make sure that more detailed information is returned to the WebClient?
Also, how can I make sure that these exceptions (in the WCF service) are logged to the EventLog? (Basically I just need to know what happened).
Take a look at HttpResponseException (namespace Microsoft.ApplicationServer.Http.Dispatcher) - they're the way where you can control the response for error cases. You can specify the status code, and you have control over the HttpResponseMessage, in which you can control the message body.
On the client side, when you call WebClient.UploadValues, wrap that call and catch a WebException. If the service returns a response with a non-successful status code (e.g., 500, 400), the Response property of the WebException will have the body, in which you can read in your client.
Another option is to use HttpClient instead of the WebClient, in which case you can simply look at the HttpResponseMessage directly.

WCF + REST: Where is the request data?

I'm currently developing a WCF RESTful service. Within the validation of the POST data, I am throwing exceptions if the request XML does not conform to our business rules.
The goal is to send an e-mail to the appropriate staff if a request comes in that considered invalid. But, along with the incoming request headers, method and URI, I'd like to also send the XML that was posted.
I have not been able to find a way to access this data. Is WCF actually destroying the request body/data before I have a chance to access it or am I missing something?
Your help is appreciated as I'm confused as to why I can't access the request data.
This unfortunately isn't supported- we had a similar need, and did it by calling internal members with reflection. We just use it in an error handler (so we can dump the raw request), but it works OK. I wouldn't recommend it for a system you don't own and operate though (eg, don't ship this code to a customer), since it can change at any time with a service pack or whatever.
public static string GetRequestBody()
{
OperationContext oc = OperationContext.Current;
if (oc == null)
throw new Exception("No ambient OperationContext.");
MessageEncoder encoder = oc.IncomingMessageProperties.Encoder;
string contentType = encoder.ContentType;
Match match = re.Match(contentType);
if (!match.Success)
throw new Exception("Failed to extract character set from request content type: " + contentType);
string characterSet = match.Groups[1].Value;
object bufferedMessage = operationContextType.InvokeMember("request",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField,
null, oc, null);
//TypeUtility.AssertType(bufferedMessageType, bufferedMessage);
object messageData = bufferedMessageType.InvokeMember("MessageData",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty,
null, bufferedMessage, null);
//TypeUtility.AssertType(jsonBufferedMessageDataType, messageData);
object buffer = jsonBufferedMessageDataType.InvokeMember("Buffer",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty,
null, messageData, null);
ArraySegment<byte> arrayBuffer = (ArraySegment<byte>)buffer;
Encoding encoding = Encoding.GetEncoding(characterSet);
string requestMessage = encoding.GetString(arrayBuffer.Array, arrayBuffer.Offset, arrayBuffer.Count);
return requestMessage;
}
So, if you declare your contract something like:
[WebInvoke(Method = "POST", UriTemplate = "create", ResponseFormat=WebMessageFormat.Json)]
int CreateItem(Stream streamOfData);
(you can use XML instead)
The streamOfData should be the body of an HTTP POST. You can deserialize it using something like:
StreamReader reader = new StreamReader(streamId);
String res = reader.ReadToEnd();
NameValueCollection coll = HttpUtility.ParseQueryString(res);
It's working like that for us, at least. You may want to use a different approach to get the string into an XMLDocument or something. This works for our JSON posts. Might not be the most elegant solution, but it is working.
I hope this helps.
Glenn
Try this,
OperationContext.Current.RequestContext.RequestMessage
Here's how you do it without reflection:
using (var reader = OperationContext.Current.RequestContext.RequestMessage.GetReaderAtBodyContents ()) {
if (reader.Read ())
return new string (Encoding.ASCII.GetChars (reader.ReadContentAsBase64 ()));
return result;
}
}
If the reader is a HttpStreamXmlDictionaryReader (as it was in my case), the class's implementation of the method ReadContentAsBase64(byte[] buffer, int index, int count) simply passes these parameters to the Stream.Read method.
Once I have the byte[] I convert the bytes to a string via ASCII encoding. For a proper implementation, you could use the content type & encoding from the message's headers to do per HTTP spec.
You could arrest the HttpApplication.Request.InputStream in a custom HttpModule of the WCF Service, read the stream and again set its position to 0 in the custom HttpModule's event handler. Then store it in session and access it further in the actual OperationContract.
For example:
public class CustomModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.AcquireRequestState +=context_AcquireRequestState;
}
void context_AcquireRequestState(object sender, EventArgs e)
{
HttpApplication application = sender as HttpApplication;
Stream str = application.Request.InputStream;
StreamReader sr = new StreamReader(str);
string req = sr.ReadToEnd();
str.Position = 0;
application.Session["CurrentRequest"] = req;
}
}