Switching ASP.Net Web API returns between XML and JSON; where's it done? - asp.net-web-api

I have an ASP.Net Web API project. In my controller is this simple 'boiler plate' API method:
using System.Web.Http;
public class DataController : ApiController
{
private static Random _Random = new Random();
[Route("api/getrandomdoubles/{count:int}")]
[AcceptVerbs("GET", "POST")]
public double[] GetRandomDoubles(int count)
{
var doubles = new double[count];
for (var i = 0; i < count; i++)
{
doubles[i] = _Random.NextDouble();
}
return doubles;
}
}
(N.B. I've cut out the other methods.)
If I call this in the browser thus http://localhost:1165/api/GetRandomDoubles/2 I get XML returned:
<ArrayOfdouble xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<double>0.3777879822895806</double>
<double>0.46401416811347668</double>
</ArrayOfdouble>
And if I call it from JavaScript thus:
d3.json('api/getrandomdoubles/2', function (error, data) {
//Do stuff
});
I get back JSON [0.6679551008473873,0.9205140638726363].
What is deciding when my controller API method returns XML and when it returns JSON? I'm guessing it is decided based on the HTTP verb, i.e. PUT or GET but I cannot see where that is specified. How would I control the return type myself?
========== EDIT ==========
I have just realised that this is browser specific. Calling http://localhost:1165/api/GetRandomDoubles/2 in IE returns JSON, calling it in Chrome returns XML.

It is called as Content Negotiation in Web API.
First, the pipeline gets the IContentNegotiator service from the HttpConfiguration object. It also gets the list of media formatters from the HttpConfiguration.Formatters collection.
Next, the pipeline calls IContentNegotiatior.Negotiate, passing in:
The type of object to serialize
The collection of media formatters
The HTTP request
The Negotiate method returns two pieces of information:
Which formatter to use
The media type for the response
If no formatter is found, the Negotiate method returns null, and the client recevies HTTP error 406 (Not Acceptable).

Related

Calling an ASP.Net RESTful POST controller API method with HTTPClient.PostAsync

A colleague has written an Azure Mobile Service API which includes the following controller method:
public class SegmentationController : ApiController
{
// [...]
// POST api/<controller>/id
public async Task<string> Post(string id)
{
// [...]
I am trying to call that from a Windows Universal app. The calls to GET methods work without issue but I am failing to call that POST method. Here is what I've tried:
response = await client.PostAsync("api/segmentation/", new StringContent(item.Id));
// 405 Method Not Allowed
response = await client.PostAsync("api/segmentation/" + item.Id, new StringContent(""));
// 500 Internal Server Error
response = await client.PostAsync("api/segmentation/", new StringContent("id=" + item.Id));
// 405 Method Not Allowed
response = await client.PostAsync("api/segmentation/", new StringContent("{\"id\":" + item.Id + "}"));
// 405 Method Not Allowed
(N.B. System.Collections.Specialized.NameValueCollection used in Marc's answer is not available on WinRT / Windows Universal.)
It is possible that my second call is correct and that the error is in the server side code; we are exploring that possibility.
What is the correct way to make a POST call to an ASP.Net RESTful API method which expects a parameter called "id" of type string?
Your parameter is the problem. You have two options:
Use a query parameter instead of body. e.g. api/segmentation?id=abc
Add [FromBody] Attribute to your parameter. e.g. public async Task<string> Post([FromBody]string id)
Now your parameter is read from body. by default only complex types are read from body.
For more details see Parameter Binding in ASP.NET Web API
It was a server error. Once we had added error reporting code we could see that the problem was the server failing to load a C++ DLL it relied on due to an x64 /x86 mismatch on Azure. The call style that now works is the second one I list in the question:
response = await client.PostAsync("api/segmentation/" + item.Id, new StringContent(""));

Compression response filter fails on breeze.js Metadata call

I have an http module where I'm adding a response filter below for compression. This works for all API calls except for 1, the call to MetaData. If I remove the [BreezeController] decoration it works fine. I think it has to do with action filter attribute that converts the string return type into an HttpResponse return type with string content.
The error I'm getting is " Exception message: The stream state of the underlying compression routine is inconsistent."
I've done some testing where a method thats defined to return an HttpResponse works fine. So I think its the scenario where the method is defined to return string, and then the action filter changes it to HttpResponse at runtime.
Any ideas how I can get this to work?
Here's the response filter being added in BeginRequest:
HttpApplication app = (HttpApplication)sender;
// Check the header to see if it can accept compressed output
string encodings = app.Request.Headers.Get("Accept-Encoding");
if (encodings == null)
return;
Stream s = app.Response.Filter;
encodings = encodings.ToLower();
if (encodings.Contains("gzip"))
{
app.Response.Filter = new GZipStream(s, CompressionMode.Compress);
app.Response.AppendHeader("Content-Encoding", "gzip");
}
Don't know the specifics of what you're doing but I know that the [BreezeController] attribute strips out filters and adds back just the ones that breeze wants.
One approach might be to define a separate controller (ModelMetadataController) that only serves the metadata. This controller doesn't have the [BreezeController] attribute; it's a plain old Web API controller.
Then you create a "Breeze controller" (ModelController) with all of the usual methods except the Metadata method.
You call the metadata controller from the client during app launch via MetadataStore.fetchMetadata just to get metadata.
Once you have populated a metadataStore in this fashion, you use it in your EntityManager which sends query and save requests to the "real" Web API data controller.
The client code might look something like this:
var ds = new breeze.DataService({
serviceName: 'breeze/Model' // the breeze query & save controller
});
var ms = new MetadataStore({
namingConvention: breeze.NamingConvention.camelCase, // assuming that's what you want
});
ms.addDataService(ds); // associate the metadata-to-come with the "real" dataService
var manager = new breeze.EntityManager({
dataService: ds,
metadataStore: ms
});
// the fun bit: fetch the metadata from a different controller
var promise = ms.fetchMetadata('breeze/ModelMetadata') // the metadata-only controller!
return promise; // wait on it appropriately

How to retrieve all requested params in .NET Web API 2?

If I want to konw what has been sent through my service, how can I get all the request parameters in Web Api2 Controller as what Request.Form.ToString() get in aspx or ashx?
The Request object is just not the Httpcontext.Request...
public HttpResponseMessage Post(HttpRequestMessage req)
{
var content = req.Content.ReadAsStringAsync().Result;
return null;
}
Also note that the request body is a non-rewindable stream; it can be read only once, you can't call ReadAsStringAsync() multiple times.

Handling batched sendgrid events using the asp.net web api

I'm attempting to use an asp.net web api application to handle batched SendGrid events and I've run into a stumbling block due to the way SendGrid handles the content type header of the post it sends.
From their documentation:
Batched event POSTs have a content-type header of application/json,
and contain exactly one JSON string per line, with each line
representing one event. Please note that currently the POST headers
define this post as application/json, though it’s not; each line is a
valid JSON string, but the overall POST body is not.
So, given a controller:
public class SendGridController : ApiController
{
// POST api/values
public void Post([FromBody]string value)
{
// do something with value
}
}
Making a post to it as SendGrid does will result in "value" being null.
string URI = "http://localhost:3018/api/sendgrid/";
string myParameters =
#"={""email"":""foo#bar.com"",""timestamp"":1322000095,""user_id"":""6"",""event"":""bounced""}
{""email"":""foo#bar.com"",""timestamp"":1322000096,""user_id"":""9"",""event"":""bounced""}";
using (var wc = new System.Net.WebClient())
{
wc.Headers[System.Net.HttpRequestHeader.ContentType] = "application/json"; // I work fine if "application/x-www-form-urlencoded" is used.
wc.UploadString(URI, myParameters);
}
If I change the content type in my client example to "application/x-www-form-urlencoded", everything works as expected.
Is there an easy way for me to override this convention such that I can handle the badly formed "json" that sendgrid provides as a string in my controller method?
Ok, I finally figured it out. The trick was to remove the "value" param and work with the request object directly.
So something like:
public class SendGridController : ApiController
{
// POST api/values
public void Post()
{
var value = Request.Content.ReadAsStringAsync().Result;
// do something with value
}
}

Serialization error in service stack when using client library

I have a ServiceStack REST service (PUT and POST) which I have tested with fiddler and if no errors are raised I return
new HttpResult(HttpStatusCode.OK);
Now I am testing the same REST service with the service stack client, I have:
var client = new XmlServiceClient("url"));
client.Post<ChangeServerLicenseDto>("", new ChangeServerLicenseDto()
{ServerName = model.ServerName});
and I get the exception on the REST service when I do
return new HttpResult(HttpStatusCode.OK)
and the error raised is :
500 (Error in line 1 position 76. Expecting element 'ChangeServerLicense'
from namespace ''.. Encountered 'Element' with name 'HttpStatusCode',
namespace 'http://schemas.datacontract.org/2004/07/System.Net'.)
My client code is in a MVC action method (POST).
My datacontract for the RestService is :
[DataContract(Name = "ChangeServerLicense", Namespace = "")]
[RestService("url", "POST", "application/xml")]
public class ChangeServerLicenseDto
{
[DataMember(Name = "ServerName", Order = 1)]
public string ServerName { get; set; }
}
The convention of signalling a successful response is to return an empty Response DTO (which by default returns a 200 OK). Also Send<TResponse>(...) does a POST so if you don't want to include the url in the request, use Send which will POST the request to the automatic pre-defined routes:
var client = new XmlServiceClient("url"));
client.Send<ChangeServerLicenseDtoResponse>(
new ChangeServerLicenseDto {ServerName = model.ServerName});
Otherwise if you still want to use .Post<T>(...) include the URL for the custom route where your services is mounted.
Note: I generally dislike using Dto suffixes on DTOs which are the most important API in your service - I explain in a bit more detail why here.

Resources