ASP.NET Core 2.1 API POST body is null when called using HttpWebRequest, seems it can't be parsed as JSON - asp.net-core

I'm facing a strange bug, where .NET Core 2.1 API seems to ignore a JSON body on certain cases.
I advised many other questions (e.g this one, which itself references others), but couldn't resolve my problem.
I have something like the following API method:
[Route("api/v1/accounting")]
public class AccountingController
{ sometimes it's null
||
[HttpPost("invoice/{invoiceId}/send")] ||
public async Task<int?> SendInvoice( \/
[FromRoute] int invoiceId, [FromBody] JObject body
)
{
// ...
}
}
And the relevant configuration is:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new TestJsonConverter());
})
.AddJsonFormatters()
.AddApiExplorer();
// ...
}
Where TestJsonConverter is a simple converter I created for testing why things doesn't work as they should, and it's simple as that:
public class TestJsonConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return token;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary (would be neccesary if used for serialization)");
}
}
Calling the api method using Postman works, meaning it goes through the JSON converter's CanConvert, CanRead, ReadJson, and then routed to SendInvoice with body containing the parsed json.
However, calling the api method using HttpWebRequest (From a .NET Framework 4, if that matters) only goes through CanConvert, then routes to SendInvoice with body being null.
The request body is just a simple json, something like:
{
"customerId": 1234,
"externalId": 5678
}
When I read the body directly, I get the expected value on both cases:
using (var reader = new StreamReader(context.Request.Body))
{
var requestBody = await reader.ReadToEndAsync(); // works
var parsed = JObject.Parse(requestBody);
}
I don't see any meaningful difference between the two kinds of requests - to the left is Postman's request, to the right is the HttpWebRequest:
To be sure, the Content-Type header is set to application/json. Also, FWIW, the HttpWebRequest body is set as follows:
using(var requestStream = httpWebRequest.GetRequestStream())
{
JsonSerializer.Serialize(payload, requestStream);
}
And called with:
var response = (HttpWebResponse)request.GetResponse();
Question
Why does body is null when used with HttpWebRequest? Why does the JSON converter read methods are skipped in such cases?

The problem was in the underlying code of the serialization. So this line:
JsonSerializer.Serialize(payload, requestStream);
Was implemented using the default UTF8 property:
public void Serialize<T>(T instance, Stream stream)
{
using(var streamWriter = new StreamWriter(stream, Encoding.UTF8) // <-- Adds a BOM
using(var jsonWriter = new JsonTextWriter(streamWriter))
{
jsonSerializer.Serialize(jsonWriter, instance); // Newtonsoft.Json's JsonSerializer
}
}
The default UTF8 property adds a BOM character, as noted in the documentation:
It returns a UTF8Encoding object that provides a Unicode byte order
mark (BOM). To instantiate a UTF8 encoding that doesn't provide a BOM,
call any overload of the UTF8Encoding constructor.
It turns out that passing the BOM in a json is not allowed per the spec:
Implementations MUST NOT add a byte order mark (U+FEFF) to the
beginning of a networked-transmitted JSON text.
Hence .NET Core [FromBody] internal deserialization failed.
Lastly, as for why the following did work (see demo here):
using (var reader = new StreamReader(context.Request.Body))
{
var requestBody = await reader.ReadToEndAsync(); // works
var parsed = JObject.Parse(requestBody);
}
I'm not very sure. Certainly, StreamReader also uses UTF8 property by default (see remarks here), so it shouldn't remove the BOM, and indeed it doesn't. Per a test I did (see it here), it seems that ReadToEnd is responsible for removing the BOM.
For elaboration:
StreamWriter and UTF-8 Byte Order Marks
The Curious Case of the JSON BOM

Related

Newtonsoft.Json Serialize / Deserialize static class

I have a static class, example:
public static Config
{
public static string ServerIP;
...
}
I made this static, because it can be easily accessed across the entire application.
The problem now is, how can I serialize / deserialize it? Because these configuration will change and using might modify the value in the json file.
Neither System.Text.Json nor Newtonsoft.Json support serialization of static classes. So while you can't serialize the class directly, you can serialize its members.
If you can use Newtonsoft.Json, then you can shim something like this at least for deserialization, and something similar for serialization:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
static class Config
{
public static string ServerIP = string.Empty;
}
static void DeserializeStaticClass(string json, Type staticClassType)
{
if (!staticClassType.IsClass)
throw new ArgumentException("Type must be a class", nameof(staticClassType));
if (!staticClassType.IsAbstract || !staticClassType.IsSealed)
throw new ArgumentException("Type must be static", nameof(staticClassType));
var document = JObject.Parse(json);
var classFields = staticClassType.GetFields(BindingFlags.Public | BindingFlags.Static);
foreach (var field in classFields)
{
var documentField = document[field.Name];
if (documentField == null)
throw new JsonSerializationException($"Not found in JSON: {field.Name}");
field.SetValue(null, documentField.ToObject(field.FieldType));
}
}
...
DeserializeStaticClass("{\"ServerIP\": \"localhost\"}", typeof(Config));
If you need to customize serialization of nested members, you can pass a JsonSerializer to documentField.ToObject.
I realize this Q is a bit old, but here's what I do...
In my static class, I make a method that materializes the class' data and returns it as an object. Then this can be used to pass in to NewtonSoft or whatever other serialization you use.
As example, here is my method in my static (INI) class:
public static dynamic Materialize() {
return new {
Web = Web,
Client = Client,
Logging = Logging
};
}
and then I can easily call this and effectively serialize my class, like so:
if(Arguments.Verbose) {
var json = JsonConvert.SerializeObject(INI.Materialize(), Formatting.Indented);
Logging.Log($"INI Configuration:\n{json}");
}

Lagom http status code / header returned as json

I have a sample where I make a client request to debug token request to the FB api, and return the result to the client.
Depending on whether the access token is valid, an appropriate header should be returned:
#Override
public ServerServiceCall<LoginUser, Pair<ResponseHeader, String>> login() {
return this::loginUser;
}
public CompletionStage<Pair<ResponseHeader, String>> loginUser(LoginUser user) {
ObjectMapper jsonMapper = new ObjectMapper();
String responseString = null;
DebugTokenResponse.DebugTokenResponseData response = null;
ResponseHeader responseHeader = null;
try {
response = fbClient.verifyFacebookToken(user.getFbAccessToken(), config.underlying().getString("facebook.app_token"));
responseString = jsonMapper.writeValueAsString(response);
} catch (ExecutionException | InterruptedException | JsonProcessingException e) {
LOG.error(e.getMessage());
}
if (response != null) {
if (!response.isValid()) {
responseHeader = ResponseHeader.NO_CONTENT.withStatus(401);
} else {
responseHeader = ResponseHeader.OK.withStatus(200);
}
}
return completedFuture(Pair.create(responseHeader, responseString));
}
However, the result I get is:
This isn't really what I expected. What I expect to receive is an error http status code of 401, and the json string as defined in the code.
Not sure why I would need header info in the response body.
There is also a strange error that occurs when I want to return a HeaderServiceCall:
I'm not sure if this is a bug, also I am a bit unclear about the difference between a ServerServiceCall and HeaderServiceCall.
Could someone help?
The types for HeaderServiceCall are defined this way:
interface HeaderServiceCall<Request,Response>
and
CompletionStage<Pair<ResponseHeader,Response>> invokeWithHeaders(RequestHeader requestHeader,
Request request)
What this means is that when you define a response type, the return value should be a CompletionStage of a Pair of the ResponseHeader with the response type.
In your code, the response type should be String, but you have defined it as Pair<ResponseHeader, String>, which means it expects the return value to be nested: CompletionStage<Pair<ResponseHeader,Pair<ResponseHeader, String>>>. Note the extra nested Pair<ResponseHeader, String>.
When used with HeaderServiceCall, which requires you to implement invokeWithHeaders, you get a compilation error, which indicates the mismatched types. This is the error in your screenshot above.
When you implement ServerServiceCall instead, your method is inferred to implement ServiceCall.invoke, which is defined as:
CompletionStage<Response> invoke()
In other words, the return type of the method does not expect the additional Pair<ResponseHeader, Response>, so your implementation compiles, but produces the incorrect result. The pair including the ResponseHeader is automatically serialized to JSON and returned to the client that way.
Correcting the code requires changing the method signature:
#Override
public HeaderServiceCall<LoginUser, String> login() {
return this::loginUser;
}
You also need to change the loginUser method to accept the RequestHeader parameter, even if it isn't used, so that it matches the signature of invokeWithHeaders:
public CompletionStage<Pair<ResponseHeader, String>> loginUser(RequestHeader requestHeader, LoginUser user)
This should solve your problem, but it would be more typical for a Lagom service to use domain types directly and rely on the built-in JSON serialization support, rather than serializing directly in your service implementation. You also need to watch out for null values. You shouldn't return a null ResponseHeader in any circumstances.
#Override
public ServerServiceCall<LoginUser, Pair<ResponseHeader, DebugTokenResponse.DebugTokenResponseData>> login() {
return this::loginUser;
}
public CompletionStage<Pair<ResponseHeader, DebugTokenResponse.DebugTokenResponseData>> loginUser(RequestHeader requestHeader, LoginUser user) {
try {
DebugTokenResponse.DebugTokenResponseData response = fbClient.verifyFacebookToken(user.getFbAccessToken(), config.underlying().getString("facebook.app_token"));
ResponseHeader responseHeader;
if (!response.isValid()) {
responseHeader = ResponseHeader.NO_CONTENT.withStatus(401);
} else {
responseHeader = ResponseHeader.OK.withStatus(200);
}
return completedFuture(Pair.create(responseHeader, response));
} catch (ExecutionException | InterruptedException | JsonProcessingException e) {
LOG.error(e.getMessage());
throw e;
}
}
Finally, it appears that fbClient.verifyFacebookToken is a blocking method (it doesn't return until the call completes). Blocking should be avoided in a Lagom service call, as it has the potential to cause performance issues and instability. If this is code you control, it should be written to use a non-blocking style (that returns a CompletionStage). If not, you should use CompletableFuture.supplyAsync to wrap the call in a CompletionStage, and execute it in another thread pool.
I found this example on GitHub that you might be able to adapt: https://github.com/dmbuchta/empty-play-authentication/blob/0a01fd1bd2d8ef777c6afe5ba313eccc9eb8b878/app/services/login/impl/FacebookLoginService.java#L59-L74

SignalR not serializing NodaTime types despite JsonSerializer settings

I am having serialization issues (exceptions) with NodaTime types and SignalR parameters such as
Error converting value to type 'NodaTime.ZonedDateTime
Error converting value \"2016-06-01T18:33:36.7279685+01 Europe/London\" to type 'NodaTime.ZonedDateTime'. Path '[0].DateCreated', line 1, position 79.
This is despite following the docs and replacing the default JsonSerializer and using the NodaTime extension methods and JSON.net nuget package e.g.
JsonSerializerSettings js = new JsonSerializerSettings();
js.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
var serializer = JsonSerializer.Create(js);
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => serializer);
Happily found a workaround from here thanks to BrannonKing
Essentially it uses a Customer Resolver for SignalR parameters which uses the correct serializer instead of creating a default.
Also referenced on SO here but of course only found that once had started to post my own question ;)
Reposting here for others googling for (the excellent) NodaTime specifically, and to share some other serialization fixes I needed, such as :
Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property X with type Y Path Z
Server Startup
public void Configuration(IAppBuilder app)
{
JsonSerializerSettings js = new JsonSerializerSettings();
js.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
js.DateParseHandling = DateParseHandling.None;
js.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
js.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
var serializer = JsonSerializer.Create(js);
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => serializer);
var resolver = new Resolver(serializer);
GlobalHost.DependencyResolver.Register(typeof(IParameterResolver), () => resolver);
}
Custom Parameter Resolver
public class Resolver : DefaultParameterResolver
{
private readonly JsonSerializer _serializer;
public Resolver(JsonSerializer serializer)
{
_serializer = serializer;
}
private FieldInfo _valueField;
public override object ResolveParameter(ParameterDescriptor descriptor, Microsoft.AspNet.SignalR.Json.IJsonValue value)
{
if(value.GetType() == descriptor.ParameterType)
{
return value;
}
if(_valueField == null)
_valueField = value.GetType().GetField("_value", BindingFlags.Instance | BindingFlags.NonPublic);
var json = (string)_valueField.GetValue(value);
using(var reader = new StringReader(json))
return _serializer.Deserialize(reader, descriptor.ParameterType);
}
}
Many thanks Brannon !

Creating JSON without quotes

A library is using Map to use some extra information. This map eventually is being converted a JSON object and I need to set request information to display for debugging purposes as this:
map.put("request", requestString);
I am considering to use Jackson specifically to create a JSON without quotes and want to set as requestString.
I am building necessary information regarding Request and building a Map including request headers, parameters, method etc.
Jackson is creating perfectly valid JSON with quotes but when I set this generated value inside map, It is displayed ugly because of having escaped quotes.
So Jackson is creating this:
{
method : "POST",
path : "/register"
}
When I set this in map, it turns to this:
{
method : \"POST\",
path : \"/register\"
}
Consider this as a huge map including all parameters and other information about request.
What I would like to want this:
{
method : POST,
path : /register
}
I know that this is not a valid JSON but I am using this as a String to a Map which is accepting String values.
public class UnQuotesSerializer extends NonTypedScalarSerializerBase<String>
{
public UnQuotesSerializer() { super(String.class); }
/**
* For Strings, both null and Empty String qualify for emptiness.
*/
#Override
public boolean isEmpty(String value) {
return (value == null) || (value.length() == 0);
}
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeRawValue(value);
}
#Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
return createSchemaNode("string", true);
}
#Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
if (visitor != null) visitor.expectStringFormat(typeHint);
}
}
and
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule("UnQuote");
module.addSerializer(new UnQuotesSerializer());
objectMapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
objectMapper.registerModule(module);
This is generating without quotes strings.
The following test passes (Jackson 2.5.0)
#Test
public void test() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Map map = new HashMap();
map.put("method", "POST");
map.put("request", "/register");
String s = mapper.writeValueAsString(map);
Map map2 = mapper.readValue(s, Map.class);
Assert.assertEquals(map, map2);
}
so your pseudo JSON without quotes does not seem the way to go

Find Matching OperationContract Based on URI

...or "How to determine which WCF method will be called based on URI?"
In a WCF service, suppose a method is invoked and I have the URI that was used to invoke it. How can I get information about the WCF end point, method, parameters, etc. that the URI maps to?
[OperationContract]
[WebGet(UriTemplate = "/People/{id}")]
public Person GetPersonByID(int id)
{
//...
}
For instance, if the URI is: GET http://localhost/Contacts.svc/People/1, I want to get this information: service name (Service), Method (GetPersonByID), Parameters (PersonID=1). The point is to be able to listen for the request and then extract the details of the request in order to track the API call.
The service is hosted via http. This information is required before the .Net caching can kick in so each call (whether cached or not) can be tracked. This probably means doing this inside HttpApplication.BeginRequest.
FYI I'm hoping to not use reflection. I'd like to make use of the same methods WCF uses to determine this. E.g. MagicEndPointFinder.Resolve(uri)
Here is what I ended up doing, still interested if there is a cleaner way!
REST
private static class OperationContractResolver
{
private static readonly Dictionary<string, MethodInfo> RegularExpressionsByMethod = null;
static OperationContractResolver()
{
OperationContractResolver.RegularExpressionsByMethod = new Dictionary<string, MethodInfo>();
foreach (MethodInfo method in typeof(IREST).GetMethods())
{
WebGetAttribute attribute = (WebGetAttribute)method.GetCustomAttributes(typeof(WebGetAttribute), false).FirstOrDefault();
if (attribute != null)
{
string regex = attribute.UriTemplate;
//Escape question marks. Looks strange but replaces a literal "?" with "\?".
regex = Regex.Replace(regex, #"\?", #"\?");
//Replace all parameters.
regex = Regex.Replace(regex, #"\{[^/$\?]+?}", #"[^/$\?]+?");
//Add it to the dictionary.
OperationContractResolver.RegularExpressionsByMethod.Add(regex, method);
}
}
}
public static string ExtractApiCallInfo(string relativeUri)
{
foreach (string regex in OperationContractResolver.RegularExpressionsByMethod.Keys)
if (Regex.IsMatch(relativeUri, regex, RegexOptions.IgnoreCase))
return OperationContractResolver.RegularExpressionsByMethod[regex].Name;
return null;
}
}
SOAP
private static void TrackSoapApiCallInfo(HttpContext context)
{
string filePath = Path.GetTempFileName();
string title = null;
//Save the request content. (Unfortunately it can't be written to a stream directly.)
context.Request.SaveAs(filePath, false);
//If the title can't be extracted then it's not an API method call, ignore it.
try
{
//Read the name of the first element within the SOAP body.
using (XmlReader reader = XmlReader.Create(filePath))
{
if (!reader.EOF)
{
XmlNamespaceManager nsManager = new XmlNamespaceManager(reader.NameTable);
XDocument document = XDocument.Load(reader);
//Need to add the SOAP Envelope namespace to the name table.
nsManager.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
title = document.XPathSelectElement("s:Envelope/s:Body", nsManager).Elements().First().Name.LocalName;
}
}
//Delete the temporary file.
File.Delete(filePath);
}
catch { }
//Track the page view.
}