Really struggling with something I hope people here can help with. I'm writing a RESTful API in Web API 2. Whenever I send a request to this service, the response is consistently being sent with a Content-Type of text/plain. Obviously this is no good, my response needs to be Content-Type of application/json. I've tried a few suggestions that I found from Google but I don't think I'm understanding the whole picture.
Is there something special I need to do in order to have my web service respond with application/json content? Note that I want this to work globally across the whole app, so I'm not after a way to modify a given response and set its content type - I want this to be a default behaviour for the whole web service: If a request contains an Accept header for application/json I want my web service to return that Content-Type instead of text/plain.
Edit to clarify:
My response contains an object called "responseData" that should be serialized into JSON and included in the body. I'm currently putting together my response like this:
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, responseData);
return response;
responseData is a POCO. This get's correctly serialized as JSON and returned in the response - the only missing part is the Content-Type which is incorrectly set to "text/plain". I could manually change this on every single response I compose, but I'm wanting to configure this on a global level.
OK, assuming that your responseData is a string, the Content-type header will be text/plain when you create the HttpResponseMessage. Doesn't matter what the contents of the string are, since no attempt is made to determine this.
The solution is to create the appropriate Content object for your message, initialized with the media type you're returning:
HttpResponseMessage response = new HttpResponseMessage()
{
Content = new StringContent(
responseData,
Encoding.UTF8,
"application/json"
)
};
There are other methods that amount to returning a particular object type and letting the API libraries serialize to JSON or XML for you as required. I prefer to let the framework do the work for me where possible, but this is how you'd achieve it with a string you've created yourself.
For a strict JSON-only result, remove the XML formatter from the WebAPI configuration and return your POCO.
In App_Start\WebApiConfig.cs, add the following to the WebApiConfig.Register method:
config.Formatters.Remove(config.Formatters.XmlFormatter);
And for your API:
public class MyObject
{
public bool result;
public string reason;
}
public class TestController : ApiController
{
public MyObject GetData()
{
MyObject result = new MyObject { result = "true", reason = "Testing POCO return" };
return result;
}
}
I ran this up and requested /api/Test from Chrome, which doesn't even mention application/json in the Accept header. Here are the response headers up until it hits Content-Type:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
And the body:
{"result":true,"reason":"Testing POCO return"}
Since I disabled XML it defaulted to JSON.
Add the following to Global.asax file.
protected void Application_Start()
{
JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new IsoDateTimeConverter());
var jsonFormatter = new JsonNetFormatter(serializerSettings);
jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
GlobalConfiguration.Configuration.Formatters.Insert(0, jsonFormatter);
}
Another possible source of the issue described is that there may be an authorization redirect in play as was the case for us when one of the engineers thought to re-use user authentication for an api.
This means incoming requests were being redirected to a login page which was the text/html response that couldn't be parsed by ReadAsync<>. A silly mistake to be sure, but not an easy one to spot.
The solution in that case was to remove the user authentication and implement HMAC based authentication for the api.
Related
I'm experiencing a very weird intermittent issue with my API calls. Sometimes the response gets truncated.
There isn't a pattern for how or when it gets truncated.
Response size is 200kb and configured response limit is 20mb.
Formatters configuration:
private static HttpConfiguration ConfigureFormatters(this HttpConfiguration config)
{
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
return config;
}
Simplified Api call:
[HttpGet, Route]
public IHttpActionResult Explore(int cityId)
{
var lists = exploreBuilderService.Build(cityId);
return Ok(lists);
}
lists type is List<SomeModel>. SomeModel is DTO with no circular references.
Sample response:
Response Headers
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 17 Jan 2020 12:02:37 GMT
Content-Length: 290248
Data
[..., {"id":47
... are other objects in the array.
It looks like json response string is cut in a random place. Our android application fails with MalformedJsonException when the response comes like this. It happens once on like 5-10 requests for the same data set.
Did you encounter a problem like this? Where should I look for potential problems causing this?
The issue was with one of our middlewares which did response interception.
Perhaps someone will face this problem now, like me.
But as it turned out, .Net 6
An ASP.NET Core web API endpoint could return a truncated JSON body with a 200 response code when an exception is thrown during serialization of the object returned by the action method if a sufficient portion of the response has already been successfully serialized beforehand, so that part of the response has already been sent to the client. Fortunately, the exception will still be logged properly. The only way to reliably prevent this is to ensure that the returned object can be successfully serialized.
I have a ASP.NET Web API, and I have been responding to request with this format,
[HttpPost]
[Route("")]
public HttpResponseMessage AlexaSkill()
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent("put json here", Encoding.UTF8);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return response;
}
and that has been working great. The issue is that there are certain situation where the requester does not expect a response. I cannot figure out how to not give a response to the requester who is posting to the url. How can I be able to return a response like a have above and also have the option to have the function not give a respons essentially acting as a void function?
You should always return a response. There's a status code 204 for when you don't want to send content in your response. From the spec:
10.2.5 204 No Content
The server has fulfilled the request but does not need to return an entity-body, and might want to return updated metainformation. The response MAY include new or updated metainformation in the form of entity-headers, which if present SHOULD be associated with the requested variant.
If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent. This response is primarily intended to allow input for actions to take place without causing a change to the user agent's active document view, although any new or updated metainformation SHOULD be applied to the document currently in the user agent's active view.
The 204 response MUST NOT include a message-body, and thus is always terminated by the first empty line after the header fields.
So your code could be something like this:
[HttpPost]
public HttpResponseMessage SomeMethod()
{
// Do things
return Request.CreateResponse(HttpStatusCode.NoContent);
}
Even a void method will return an HTTP status code to the client invoking the API. See this link
You'll probably need to ask for changes or another alternative to your client.
If you want to just terminate the request, try this:
HttpContext.Current.Response.End();
throw new Exception("Terminating request.");
It seems like a strange thing for an HTTP server to do, but if that's what you really need, give that a shot. If you follow by throwing an exception, then an error won't be sent to the client because you've already ended the response.
I'm writing two small pieces of C# code. The first is for a client-side Portable Class Library. All it does is send messages to an Azure Service Bus topic via the Azure Service Bus REST API, using HttpClient.
I populate the BrokerProperties header on the REST call with valid JSON, and I expect that on the server side, when I receive the message through a subscription, that I'll get my instance of BrokeredMessage.Properties populated with the values I sent from the client.
The one problem I've had on this side is that the documentation says to set Content-Type to application/atom+xml;type=entry;charset=utf-8, but even when I do I get application/json; charset=utf-8, so I'm just using application/json.
With that aside, as far as I can tell, this does what it's supposed to do. It creates the client and the request message, sets the headers, and sends the message. I get a 201 Created every time. Here's all of it:
private async static void SendServiceBusMessage(Command command)
{
// Create the HttpClient and HttpRequestMessage objects
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, topicUri);
// Add the authorization header (CreateAuthToken does the SHA256 stuff)
request.Headers.Add("Authorization", CreateAuthToken(topicUri, authSasKeyName, authSasKey));
// Add the content (command is a normal POCO)
// I've tried application/atom+xml;type=entry;charset=utf-8, always see application/json in the request
request.Content = new StringContent(JsonConvert.SerializeObject(command), Encoding.UTF8, "application/json");
// Add the command name and SessionId as BrokeredMessage properties
var brokeredMessageProperties = new Dictionary<string, string>();
brokeredMessageProperties.Add("CommandName", command.GetType().Name);
brokeredMessageProperties.Add("SessionId", Guid.NewGuid().ToString());
// Add the BrokerProperties header to the request
request.Content.Headers.Add("BrokerProperties", JsonConvert.SerializeObject(brokeredMessageProperties));
// I've also tried adding it directly to the request, nothing seems different
// request.Headers.Add("BrokerProperties", JsonConvert.SerializeObject(brokeredMessageProperties));
// Send it
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
// Do some error-handling
}
}
and here's an example of the HTTP request it sends. Compare it to the example at the bottom of Send Message documentation... aside from the Content-Type, it looks (functionally) identical to me.
POST https://myawesomesbnamespace.servicebus.windows.net/commands/messages HTTP/1.1
Authorization: SharedAccessSignature sr=https%3A%2F%2Fmyawesomesbnamespace.servicebus.windows.net%2Fcommands%2Fmessages&sig=SomeValidAuthStuffHere
Content-Type: application/json; charset=utf-8
BrokerProperties: {"CommandName":"CreateJob_V1","SessionId":"94932660-54e9-4867-a020-883a9bb79fa1"}
Host: myawesomesbnamespace.servicebus.windows.net
Content-Length: 133
Expect: 100-continue
Connection: Keep-Alive
{"JobId":"6b76e7e6-9499-4809-b762-54c03856d5a3","Name":"Awesome New Job Name","CorrelationId":"47fc77d9-9470-4d65-aa7d-690b65a7dc4f"}
However, when I receive the message on the server, the .Properties are empty. This is annoying.
The server code looks like this. It just gets a batch of messages and does a foreach loop.
private async Task ProcessCommandMessages()
{
List<BrokeredMessage> commandMessages = (await commandsSubscriptionClient.ReceiveBatchAsync(serviceBusMessageBatchSize, TimeSpan.FromMilliseconds(waitTime_ms))).ToList();
foreach (BrokeredMessage commandMessage in commandMessages)
{
// commandMessage.Properties should have CommandName and SessionId,
// like I sent from the client, but it's empty
// that's not good
if (commandMessage.Properties.ContainsKey("CommandName"))
{
string commandName = commandMessage.Properties["CommandName"] as string;
// Do some stuff
}
else
{
// This is bad, log an error
}
}
}
So, I'm a bit stuck. Can anyone spot something I'm doing wrong here? Maybe it's the Content-Type problem and there's a way around it?
Thanks!
Scott
Seattle, WA, USA
OK, finally getting back to this. What I misunderstood (and I'd argue the documentation isn't clear about) is that arbitrary properties cannot be passed through the BrokerProperties header. Only named properties from the BrokeredMessage class (like SessionId, Label, etc.) will come through Service Bus to the server.
For properties to show up in BrokeredMessage.Properties, they have to be passed as custom headers on the request. So, in my case,
request.Headers.Add("CommandName", command.GetType().Name);
gets the CommandName property to show up on the server after the message is passed through Service Bus.
And to pass the SessionId value, I'll still want to pass it through BrokerProperties header.
I'm checking out web api's content negotiation and custom formatters and trying to use a generic custom formatter. Using RazorEngine I am parsing a razor view to be returned as a response message from a web api controller.
Here is my formatter configuration
GlobalConfiguration.Configuration.Formatters.Insert(0, new RazorViewFormatter<DefaultViewModel>("home-json", new MediaTypeHeaderValue("text/json"), new MediaTypeHeaderValue("application/json")));
GlobalConfiguration.Configuration.Formatters.Add(new RazorViewFormatter<DefaultViewModel>("home", new MediaTypeHeaderValue("text/html")));
GlobalConfiguration.Configuration.Formatters.Add(new RazorViewFormatter<IEnumerable<CategoryViewModel>>("categories-json", new MediaTypeHeaderValue("text/json"), new MediaTypeHeaderValue("application/json")));
GlobalConfiguration.Configuration.Formatters.Add(new RazorViewFormatter<IEnumerable<CategoryViewModel>>("categories", new MediaTypeHeaderValue("text/html")));
Making a request with to my default resource, returns the correctly parsed razor view.
GET http://localhost:56071/api/ HTTP/1.1
User-Agent: Fiddler
Accept: text/json
Host: localhost:56071
However making a request to my categories resource, just returns the serialized json of my model rather than the parsed template. Essentially missing the mark the on custom media formatter. WriteToStreamAsync() is never called
GET http://localhost:56071/api/categories HTTP/1.1
User-Agent: Fiddler
Accept: text/json
Host: localhost:56071
Is this not a workable approach? I do get a match on CanReadType, I'm wondering if the multiple formatters and they way conneg is evaluated ends up somehow not matching text/json.
Any thoughts are appreciated. Thanks.
Update
public override bool CanWriteType(Type type)
{
return (type == typeof (T) || type.IsSubclassOf(typeof (T)));
}
public override bool CanReadType(Type type)
{
return false;
}
I have registered a new content type in ServiceStack with:
appHost.ContentTypeFilters.Register("application/x-my-content-type",
SerializeToStream, DeserializeFromStream);
And everything works as expected, if the client sends the content type in the http stream.
Unfortunately, I have a client that is not in my control of HTTP Request Heads and does not send the content type.
How can I get ServiceStack to set the default content type for that route?
On every ServiceStack /metadata page lists the different ways a client can request a specific Content-Type:
To override the Content-type in your clients HTTP Accept Header, append ?format=xml or add .format extension
E.g. The client can specify your custom ContentType with ?format=x-my-content-type, adding .x-my-content-type extension or by specifying the HTTP Header (in the HttpClient):
Accept: application/x-my-content-type
Otherwise if your HttpClient doesn't send an Accept header you can specify the default content type in your AppHost with:
SetConfig(new HostConfig {
DefaultContentType = "application/x-my-content-type"
});
Note: All Configuration options in ServiceStack are set on HostConfig.
The issue when calling web services from a web browser is that they typically ask for Accept: text/html which by contract ServiceStack obliges by returning back HTML if it is enabled.
To ensure your Content-Type is always returned you may also want to disable the HTML feature with:
SetConfig(new HostConfig {
EnableFeatures = Feature.All.Remove(Feature.Html),
});
Otherwise if you want to override the Accept header you can force your service to always return your Content-Type by decorating your Response DTO inside a HttpResult, i.e:
return new HttpResult(dto, "application/x-my-content-type");
Otherwise anywhere outside of your Service (e.g. Request/Response filter) you can set the Response ContentType anywhere that has access to a IHttpRequest with:
httpReq.ResponseContentType = "application/x-my-content-type";