I'm using Json.net and all I need from it is the simplest form of creating a JSON string to send up as an HTTP POST. For that reason, I don't want to create a new custom object just to be able to serialize it correctly. So I chose the dynamic method in Json.net.
The JSON that I need looks something like this:
{
root: {
header: {
namespace: "",
name: ""
},
body: {
email: email
myArray:[
{
item1: "",
item2: ""
},
{
item3: "",
item4: ""
},
]
}
}
}
So I tried to do the following:
dynamic httpRequestBody = new JObject();
httpRequestBody.root = new JObject();
httpRequestBody.root.header = new JObject();
httpRequestBody.root.header.namespace = "";
httpRequestBody.root.header.name = "name;
But since "namespace" is a C# keyword, it doesn't let me use it. Is there a way to get around it since I need "namespace" as part of the JSON? If not, what's the simplest way of creating this JSON string?
To clarify, I don't have a very strong opinion against creating my own little class and serialize it, but it feels like since all i need is to send some jSON up and forget about it, I should be able to create it on the fly.
Rather than upcasting your JObject to be dynamic, keep it as an explicitly typed variable. JObject implements IDictionary<string, JToken> which can be used, along with the implicit conversion operators from primitive types to JToken, to set a property value of any name to any primitive value or other JToken:
var header = new JObject();
header["namespace"] = ""; // Here we use the implicit operator from string to JToken
header["name"] = "name";
httpRequestBody["root"]["header"] = header;
Using explicit typing also allows for compile-time checking of code correctness, and may improve performance.
Demo fiddle here.
Related
I have dotnet WebAPI and I'm trying to get a specific behaviour but am constantly getting 415 responses.
I have reproduced this by starting a new webapi project using dotnet new webapi on the command line. From there, I added two things: a new controller, and a model class. In my real project the model class is obviously a bit more complex, with inheritance and methods etc...
Here they are:
[HttpGet("/data")]
public async Task<IActionResult> GetModel(BodyParams input)
{
var response = new { Message = "Hello", value = input.valueOne };
return Ok(response);
}
public class BodyParams {
public bool valueOne { get; set; } = true;
}
My goal is that the user can call https://localhost:7222/data with no headers or body needed at all, and will get the response - BodyParams will be used with the default value of true. Currently, from postman, or from the browser, I get a 415 response.
I've worked through several suggestions on stack and git but nothing seems to be working for me. Specifically, I have tried:
Adding [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] into the controller, but this makes no difference unless I provide an empty {} json object in the body. This is not what I want.
Making BodyParams nullable - again, no change.
Adding .AddControllers(opt => opt.AllowEmptyInputInBodyModelBinding = true)... again, no change.
I Implemented the solution suggested here using the attribute modification in the comment by #HappyGoLucky. Again, this did not give the desired outcome, but it did change the response to : 400 - "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true."
I tried modifying the solution in (4) to manually set context.HttpContext.Request.Body to an empty json object... but I can't figure out the syntax for this because it need to be a byte array and at that point I feel like I am way over complicating this.
How can I get the controller to use BodyParams with default values in the case that the user provides no body and no headers at all?
You can achieve that using a Minimal API.
app.MapGet("/data",
async (HttpRequest httpRequest) =>
{
var value = true;
if (Equals(httpRequest.GetTypedHeaders().ContentType, MediaTypeHeaderValue.Parse("application/json")))
{
var bodyParams = await httpRequest.ReadFromJsonAsync<BodyParams>();
if (bodyParams is not null) value = bodyParams.ValueOne;
}
var response = new {Message = "Hello", value};
return Results.Ok(response);
});
So, as there doesn't seem to be a more straightforward answer, I have currently gone with the approach number 5) from the OP, and just tweaking the code from there very slightly.
All this does is act as an action which checks the if the user has passed in any body json. If not, then it adds in an empty anonymous type. The behaviour then is to use the default True value from the BodyParams class.
The full code for the action class is:
internal class AllowMissingContentTypeForEmptyBodyConvention : Attribute, IActionModelConvention
{
public void Apply(ActionModel action)
{
action.Filters.Add(new AllowMissingContentTypeForEmptyBodyFilter());
}
private class AllowMissingContentTypeForEmptyBodyFilter : IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (!context.HttpContext.Request.HasJsonContentType()
&& (context.HttpContext.Request.ContentLength == default
|| context.HttpContext.Request.ContentLength == 0))
{
context.HttpContext.Request.ContentType = "application/json";
var str = new { };
//convert string to jsontype
var json = JsonConvert.SerializeObject(str);
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
context.HttpContext.Request.Body = new MemoryStream(requestData);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
// Do nothing
}
}
}
Then you can add this to any of your controllers using [AllowMissingContentTypeForEmptyBodyConvention]
I am working with Auth0 Management API endpoints, the issue with this one.
https://auth0.com/docs/api/management/v2?_ga=2.197148647.957265821.1601726170-1678439180.1588036522#!/Users/get_users
Here is my rest code.
var client = new RestClient(tempapiendpoint);
var request = new RestRequest(Method.GET);
request.AddHeader(header, bearerstring);
request.AddParameter(specificfieldname,specificfields);
request.AddParameter(includefieldname, includetrueorfalse);
IRestResponse response = await client.ExecuteAsync(request);
var myDeserializedClass = JsonConvert.DeserializeObject<Root>(response.Content);
I have the following response.
[
{
"email": "somevalue",
"name": "somevalue",
"nickname": "somevalue",
"user_id": "somevalue"
},
{
"email": "somevalue",
"name": "somevalue",
"nickname": "somevalue",
"user_id": "somevalue"
},
{
"email": "somevalue",
"name": "somevalue",
"nickname": "somevalue",
"user_id": "somevalue"
}
]
At this point, I use an online class generator, such as, https://json2csharp.com/
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class MyArray {
[JsonProperty("email")]
public string Email;
[JsonProperty("name")]
public string Name;
[JsonProperty("nickname")]
public string Nickname;
[JsonProperty("user_id")]
public string UserId;
}
public class Root {
[JsonProperty("MyArray")]
public List<MyArray> MyArray;
}
and everytime, I get the same error.
Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'RandomStuffGeneratorPrivate.POCO.Root' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path '', line 1, position 1.
A few more things I have tried.
I have tried another source for class generation, https://app.quicktype.io/. I get the exact same error.
I have checked the json for validity. it is in the correct format.
even if hand map it, this is a straight forward json.
Further, I noticed that the name of the user class and the collection name are both the same. So, I changed the name for the user class. (it never gave any errors while debugging but I changed it anyway). no change in error
When generating classes, I am taking the json string directly from the live response during debugging, from IRestResponse response.content, just in case the online API documentation is making a mistake.
i have looked at other stack questions, and in those cases, I noticed that there was some mistake related to not having a list. Here, I definitely have mapped (the online generators wont make such a mistake) the returning array to a list.
Looks to me that you are deserializing to the wrong class, try using : JsonConvert.DeserializeObject<MyArray[]>(myJsonResponse)
(looks like Thomas has already provided a solution which i also discovered on my own. Posting some additional details here.)
Ultimately, this seems to have worked for me.
var myDeserializedClass = JsonConvert.DeserializeObject<List<SingleUser>>(response.Content);
I think, the issue was, I was looking for a solution, that would me something like this.
myDeserializedClass
and, I could use it, like this,
var nameOfPerson = myDeserializedClass.MyArray[0].Name;
But, i think that is not how JSON works. Perhaps, if there were other fields in the JSON body, with a mix and match of collection and non-collection values. Since, this was a complete collection, the outer layer gets eliminated or something.
Further, Quicktype class generator clearly says this at the top.
// <auto-generated />
//
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
// using RandomStuffGeneratorPrivate.POCO;
//
// var allUsers445 = AllUsers445.FromJson(jsonString);
and, the function is defined like this.
public static List<AllUsers445> FromJson(string json) => JsonConvert.DeserializeObject<List<AllUsers445>>(json, RandomStuffGeneratorPrivate.POCO.Converter.Settings);
Recently the IFormCollection in the platform I'm building started returning values of the type Microsoft.Extensions.Primitives.StringValues. when it used to return strings.
The controllers were made with strings in mind and now that are a lot of forms that are not working.
Is there any explanation to this, or a way to revert it?
As far as I'm aware ASP.NET Core's IFormCollection has always been a collection of StringValues. The reason is simple: multiple values can be posted for any particular key, making it potentially impossible to set the value if the type was merely string. There is no way to "revert" this. Change your code accordingly.
Or, better yet, stop using IFormCollection. Bind to strongly-typed models. That's always the best way.
For others coming here also confused by seeing IFormCollection giving StringValues. You may be familiar with .NET Frameworks FormCollection class, which gives strings. The reason for the change is valid and explained by #Chris Pratt in his answer here.
To make IFormCollection and StringValues feel familiar again consider any of these simple extensions:
// Example: var name = collection["name"].FirstOrNull();
// Equal to (.NET Framework): var name = collection["name"];
public static string FirstOrNull(this StringValues values)
{
if (values.Count > 0)
{
return values[0];
}
return null;
}
// Example: var name = collection["name"].FirstOr("John Doe");
// Equal to (.NET Framework): var name = collection["name"] ?? "John Doe";
public static string FirstOr(this StringValues values, string fallback)
{
if (values.Count > 0)
{
return values[0];
}
return fallback;
}
// Example: var name = collection.ValueOrFallback("name", "John Doe");
// Equal to (.NET Framework): var name = collection["name"] ?? "John Doe";
public static string ValueOrFallback(this IFormCollection collection, string key, string fallback)
{
if (collection[key].Count > 0)
{
return collection[key][0];
}
return fallback;
}
Also consider the built-in TryGetValue:
if (collection.TryGetValue("name", out var name))
{
// at least one name did exist
}
Alt.
var name = collection.TryGetValue("name", out var names) ? names[0] : "John Doe";
I have few empty route values I want in the query string:
var routeValues = new RouteValueDictionary();
routeValues.Add("one", "");
routeValues.Add("two", null);
routeValues.Add("three", string.Empty);
If I then pass it to UrlHelper.RouteUrl() it ignores all the values and the generated query string is empty. However, urls like /?one=&two=&three= are perfectly valid. How can I do that?
This behavior is built into the default Route class. It calls into the ParsedRoute.Bind() method where it does this check:
if (IsRoutePartNonEmpty(obj2))
{
acceptedValues.Add(key, obj2);
}
Which does this:
private static bool IsRoutePartNonEmpty(object routePart)
{
string str = routePart as string;
if (str != null)
{
return (str.Length > 0);
}
return (routePart != null);
}
This effectively prevents any query string values from being output if they are empty. Everything it does is private, static, internal, and otherwise impossible to override. So, there are really only 2 options to override this behavior.
Subclass Route and override GetVirtualPath(), replacing ParsedRoute.Bind() with a custom implementation.
Subclass RouteBase and create a custom route implementation.
I guess the 3rd option is to leave it alone, as others have pointed out this isn't really a problem since having an empty parameter in the query string has little or no value.
Is there a way to deserialize an array sent by jquery post method to directly c# string array(string[])?
I tried posting data like this
$.post(url,
{
'selectedTeams[]'=['Team1','Team2']
},
function(response) {}, 'json');
And trying to consume it like this in C# class
string jsonData = new StreamReader(context.Request.InputStream).ReadToEnd();
var selectedTeams = new JavaScriptSerializer().Deserialize<string[]>(jsonData);
It didn't work and ofcource it should not as there is no property selectedTeams[] in string[]
I am aware of the way to define a class something like this
class Teams
{
public string[] SelectedTeams{get;set;}
}
and then do the deserialization.
But I think that is an unnecessary defining a class so isn't there a way to directly convert json array to c# string array
Thanks in advance.
Figure it out!
Using stringified array object rather than direct named json parameter to pass as data solved my problem
I am now posting like this
var Ids = new Array();
Ids.push("Team1");
Ids.push("Team2");
$.post(url, JSON.stringify(Ids), function(response) {}, 'json');
And now able to deserialize json response directly to string array like this
string jsonData = new StreamReader(context.Request.InputStream).ReadToEnd();
var selectedTeams = new JavaScriptSerializer().Deserialize<string[]>(jsonData);
Thanks!!
you could develop your own class, but I would suggest you use this:
http://json.codeplex.com/