ASP.NET Core - weird behavior with [FromBody] - c#

I have an ASP.NET Core web app that works fine, when using this controller implementation:
[HttpPost]
public ActionResult SetAssignment()
{
string b = "";
using (StreamReader rdr = new StreamReader(Request.Body)) b = rdr.ReadToEnd();
Assignment asg = JsonConvert.DeserializeObject<Assignment>(b);
// asg is valid here
// ... do other stuff ...
}
But it does NOT work when using this implementation:
[HttpPost]
public ActionResult SetAssignment([FromBody] Assignment asg)
{
// asg is always NULL here
// ... do other stuff...
}
Theoretically, these implementations should be identical (although the second would be way more elegant), no?
I seem to be missing something...
The way this method is called looks like this, btw:
function asgupdate() {
var asp = {};
asp.AssignmentId = $("#asp_idx").val();
...
$.ajax({
type: "POST",
url: "/Project/SetAssignment",
data: JSON.stringify(asp),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (r) {
...
}
});
};
The JSON request does get sent by the browser and it look correct (afaict).
The assignment model looks like this:
public class Assignment
{
[Key]
public int AssignmentId { get; set; }
[Required]
public int CrewMemberId { get; set; }
[Required]
public int ProjectId { get; set; }
[DisplayName("Function")]
public string Function { get; set; } = "";
public string? Account { get; set; }
public float Rate { get; set; } = 0;
public float PrepWeeks { get; set; } = 0;
public float ShootWeeks { get; set; } = 0;
public float WrapWeeks { get; set; } = 0;
public float PostWeeks { get; set; } = 0;
public DateTime Created { get; set; } = DateTime.UtcNow;
public DateTime Updated { get; set; } = DateTime.UtcNow;
public AssignmentTypes? AssignmentType { get; set; } = AdditionalClasses.AssignmentTypes.SALARYNORMAL;
public virtual CrewMember CrewMember { get; set; }
public virtual Project? Project { get; set; }
}

Ok, the solution was a mix of several things, thanks to #dbc for pointing me in the right direction...
I wrongly assumed that the automatic JSON conversion would be identical to Newtonsoft.JSON, which it is definitely NOT.
Since System.Text.Json is more strict in how it interprets stuff, I had to add some options:
builder.Services.AddControllers().AddJsonOptions(x =>
{
x.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
x.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});
Since Enums are also treated differently (the NumberHandling options does NOT apply to Enum parsing, I had to change the generating JavaScript to always send an integer instead of a string. I tried to use JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()), however that broke the sending back of the enum (because it would now send the string name of the enum instead of the integer value back to the webfrontend, which couldn't deal with it).

Related

.NET Core API REST C# List into List is null

I'm developing an api in net core.
I've done a post function in which I send an object containing multiple parameters and a list within another list.
When I'm debugging the code the function is called correctly but I find that the second list always arrives null.
The rest of the data arrives at you correctly. I have done different tests with other objects and everything works correctly.
It is this case in which the list within another the second one arrives null.
My code:
example request input
{
"Name": "TestName",
"Related1":
[{
"id1": "TestNameRelated1",
"Related2":
[{
"id2": "TestNameRelated2"
}]
}]
}
[HttpPost]
public resultExample Test([FromBody]TestClass test)
{
//do something
}
[DataContract]
public class TestClass
{
[DataMember]
public string Name { get; set; }
[DataMember]
public List<TestClassArray> Related1 { get; set; }
}
[DataContract]
public class TestClassArray
{
[DataMember]
public string id1 { get; set; }
[DataMember]
public List<TestClassArray2> Related2 { get; set; }
}
[DataContract]
public class TestClassArray2
{
[DataMember]
public string id2 { get; set; }
}
This api was previously made in .NET framework 4.8 and this case worked correctly.
Now I'm passing the api to .Net5.
Could it be that in .Net5 it is not allowed to pass lists within other lists?
Do you have to enable some kind of configuration to be able to do this now?
You need use class/DTO with constructor like shown below and you should be good to go. I have uploaded this sample API app's code working with .net5.0 on my GitHub here.
public class TestClass
{
public TestClass()
{
Related1 = new List<TestClassArray>();
}
public string Name { get; set; }
public List<TestClassArray> Related1 { get; set; }
}
public class TestClassArray
{
public TestClassArray()
{
Related2 = new List<TestClassArray2>();
}
public string id1 { get; set; }
public List<TestClassArray2> Related2 { get; set; }
}
public class TestClassArray2
{
public string id2 { get; set; }
}
public class ResultExample
{
public string StatusCode { get; set; }
public string Message { get; set; }
}
Controller Post Method
[HttpPost]
[ProducesResponseType(typeof(ResultExample), 200)]
public ResultExample Post([FromBody] TestClass test)
{
ResultExample testResult = new ResultExample();
TestClass test2 = new TestClass();
TestClassArray testClassArray = new TestClassArray();
TestClassArray2 testClassArray2 = new TestClassArray2();
test2.Name = test.Name;
foreach (var item in test.Related1)
{
foreach (var item2 in item.Related2)
{
testClassArray2.id2 = item2.id2;
}
testClassArray.Related2.Add(testClassArray2);
}
test2.Related1.Add(testClassArray);
Console.WriteLine(test2);
testResult.Message = "New Result added successfullly....";
testResult.StatusCode = "201";
return testResult;
}
Swagger Input Sample Payload
Post Controller Result
Response of Sample input payload,(You can change it to default 201 response code as well)
I had a similar issue.
API method shows List was null
In my case a date field was not well formatted
So I use SimpleDateFormat on Android Studio with a correct datetime format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",Locale.US);
item.setDate(dateFormat.format(calendar.getTime()));
and works fine

Asp.NetCore API Controller not getting Data from Json

I have 2 applications, one which posts data to another. When I run the first application the post method in the controller executes but the model or ObjavaDto (objaveList) can't be found so it's null. When I copy-paste the json from var json into Postman everything works. What am I missing?
var json = new JavaScriptSerializer().Serialize(objaveList[2]);
I used [2] just for simplicity reasons because there are a lot of them
string url = "http://localhost:61837/api/Objave";
string result;
using (var client = new WebClient())
{
client.Headers.Add("Content-Type", "application/json");
result = client.UploadString(url, "POST", json);
}
2nd application Controller
namespace StecajeviInfo.Controllers.Api
{
[Route("api/[controller]")]
public class ObjaveController : Controller
{
[HttpPost]
public void Post([FromBody]ObjavaDto objaveList)
{
}
}
}
public class ObjavaDto
{
public string OznakaSpisa { get; set; }
public string NazivOtpravka { get; set; }
public string NazivStecajnogDuznika { get; set; }
public string PrebivalisteStecajnogDuznika { get; set; }
public string SjedisteStecajnogDuznika { get; set; }
public string OIBStecajnogDuznika { get; set; }
public string OglasSeOdnosiNa { get; set; }
public DateTime DatumObjave { get; set; }
public string OibPrimatelja { get; set; }
public string Dokument { get; set; }
}
Sent data looks like this
{
"OznakaSpisa":"St-6721/2015",
"NazivOtpravka":"Rješenje - otvaranje stečajnog postupka St-6721/2015-7",
"NazivStecajnogDuznika":"RAIN AIR d.o.o.",
"PrebivalisteStecajnogDuznika":"Savska 144/A, 10000, Zagreb",
"SjedisteStecajnogDuznika":"",
"OIBStecajnogDuznika":‌​"37144498637",
"Oglas‌​SeOdnosiNa":"Missing Oib",
"DatumObjave":"\/Date(1501106400000)\/",
"OibPrimatelja"‌​:"37144498637",
"Doku‌​ment":"e-oglasna.pra‌​vosudje.hr/sites/def‌​ault/files/ts-zg-st/‌​…;"
}
Thank you all for your replies, you have been very helpful and gave me an idea how to test. I tested with commenting out properties and I found out it's because of the special characters in Naziv otpravka ("Rješenje" and "stečajnog") which are luckily present only in that property.
I found that this solved the problem https://stackoverflow.com/a/12081747/6231007
client.Headers["Content-Type"] = "application/json; charset=utf-8";
client.UploadDataAsync(new Uri(url), "POST",
Encoding.UTF8.GetBytes(json));
Datetime is problematic. Make it nullable (DateTime?) and test with that. You'll probably get all other properties filled and datetime will stay null. If that's the problem, make sure your client sends datetime format that your model binder understands.

502 error while converting entity framework data to json. Possible recursion. How to prevent it?

[HttpGet("/api/notes/suggested")]
public JsonResult GetSuggestedNotes(string searchText)
{
//TODO: Podpowiedzi przy wpisywaniu tytułu
JsonResult result = null;
try {
List<Note> n = db.Notes.Include(x => x.NoteTags).ToList();
result = Json(n);
}
catch(Exception e)
{
Console.WriteLine(e);
}
return result;
}
public class Note
{
public Note()
{
CreationDate = DateTime.Now;
NoteTags = new HashSet<NoteTag>();
Parts = new HashSet<Part>();
}
public int ID { get; set; }
public virtual ICollection<NoteTag> NoteTags { get; set; }
public virtual ICollection<Part> Parts { get; set; }
public DateTime? CreationDate { get; set; }
[NotMapped]
public string TagsToAdd { get; set; }
[NotMapped]
public string TagsAsSingleString {
get
{
string result = "";
foreach(var nt in NoteTags)
{
result += nt.Tag.Name + " ";
}
return result;
}
}
}
public class NoteTag
{
public int NoteId { get; set; }
public virtual Note Note { get; set; }
public int TagId { get; set; }
public virtual Tag Tag { get; set; }
}
When I try to get data using this WebAPI controller, I get 502 bad gateway. No errors, everything's fine while debugging server. Data get from database correctly.
I suspect that it could be something similar to "infinite loop" but how to prevent it? (Note class is connected to collection of NoteTag objects that are connected back to Note which probably makes this loop).
And why there are no errors if something went wrong? :/
I don't know if it still relevant but i had the same problem and what worked for me it to Configure Newtonsoft.Json
SerializerSettings.ReferenceLoopHandling = ewtonsoft.Json.ReferenceLoopHandling.Ignore.
If you are using VS2015 MVC you can add the following code:
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
in the ConfigureServices method in the Startup class.
I think the problem its recursion, can you try with an Anonymous type
NoteTags has Note , imagine if the Note->NoteTags->Note->NoteTags->Note->NoteTags ...
`List n = db.Notes.Include(x => x.NoteTags).ToList();
var e = n.select(x=> new {property=value});
result = Json(e);`

MVC WEB API Controller C# JSON Deserialize not working

I have been struggling with my first c# project. This is a web api controller that will receive a post from AngularJS. The post is coming across but appears to have an extra set of brackets in the JSON object (right click/copy value in vs 2015) . I have tried various methods but everytime I deserialize the JSON object I get null values.
public class LocationsTestController : ApiController
{
[System.Web.Http.HttpPost]
[Route("")]
public IHttpActionResult Post(object json)
{
string sjson = json.ToString();
Coords oCoords = JsonConvert.DeserializeObject<Coords>(sjson);
DBEntities db = new DBEntities();
db.spUpdateLocation(User.Identity.Name, oCoords.latitude.ToString(), oCoords.longitude.ToString());
db.SaveChanges();
return Ok(oCoords.latitude); //return trash data for now
}
}
Here is my JSON copied from the the object received by the post.
{{
"coords": {
"latitude": 43.445969749565833,
"longitude": -80.484091512936885,
"altitude": 100,
"accuracy": 150,
"altitudeAccuracy": 80,
"heading": 38,
"speed": 25
},
"timestamp": 1442113213418
}}
I have tried mapping to the entity framework, but problem is I need to add the authenticated username to the json object as I don't want to trust what is being passed to the API.
Any help would be much appreciated.
Try to receive a model instead of plain string in your WebApi method:
public class CoordsItemModel
{
public double latitude { get; set; }
public double longitude { get; set; }
public int altitude { get; set; }
public int accuracy { get; set; }
public int altitudeAccuracy { get; set; }
public int heading { get; set; }
public int speed { get; set; }
}
public class CoordsModel
{
public CoordsItemModel coords { get; set; }
public long timestamp { get; set; }
}
[System.Web.Http.HttpPost]
[Route("")]
public IHttpActionResult Post(CoordsModel model)
{
DBEntities db = new DBEntities();
db.spUpdateLocation(User.Identity.Name, model.coords.latitude.ToString(), model.coords.longitude.ToString());
db.SaveChanges();
return Ok(model.coords.latitude.ToString()); //return trash data for now
}
There might be some issues with the way you are posting from client side. It's hard to check without the client code and what exactly is coming over network (in fiddler for example).
Generally the web api should be able to get the object directly without you deserializing it. So, if the client is posting proper json, this should work
public IHttpActionResult Post(Coords oCoords)
{
//use oCoords directly
}
If that doesn't work, try changing your web api method to receive string rather than an object, and deserialize that string.
public IHttpActionResult Post(string jsonString)
{
Coords oCoords = JsonConvert.DeserializeObject<Coords>(jsonString);
//rest of the code
}
If you still have double braces, you can do this before you deserialize
jsonString = jsonString.Replace("{{", "{").Replace("}}", "}");
But, obviously, this is not the correct fix. The issue still remains somewhere.
Use It.
public class coordsSample
{
public string latitude { get; set; }
public string longitude { get; set; }
public int altitude { get; set; }
public int accuracy { get; set; }
public int altitudeAccuracy { get; set; }
public int heading { get; set; }
public int speed { get; set; }
}
public class coords1
{
public coordsSample coords { get; set; }
public long timestamp { get; set; }
}
static void Main(string[] args)
{
var jsonFormat = #"[{""timestamp"":""1442113213418"",""coords"":{
""latitude"":""43.445969749565833"",""longitude"":""-80.484091512936885"",""altitude"":""100"",""accuracy"":""150""
,""altitudeAccuracy"":""80"",""heading"":""38"",""speed"":""25""}}]";
var result = JsonConvert.DeserializeObject<coords1>(jsonFormat.Substring(1).Substring(0, jsonFormat.Length - 2));
Console.WriteLine(result.coords.latitude);
Console.WriteLine(result.coords.longitude);
}
Try Like that it will help for you.because those coords you take in json it trated as a properties so for that you need to create a new class and assign this properties in inside it.Thanks

MVC JSON object with array not set properly

I am making a tool in which one can get a Quotation throw javascript and then send himself that quotation. The making of the quotation is going fine but when sending the email, the quotation data is corrupt.
The data in the Quotation object is fine except for the Options array. When sending 3 array items the Options array holds 3 items except for their names are null and the price is 0.
The quotation is send to ASP.NET MVC 3 using jQuery.post.
Quotation object in C# looks like:
public class Quotation
{
public string Email { get; set; }
public string Product { get; set; }
public int Amount { get; set; }
public decimal BasePrice { get; set; }
public decimal SendPrice { get; set; }
public Option[] Options { get; set; }
public decimal Discount { get; set; }
public decimal SubTotal { get; set; }
public decimal TotalPrice { get; set; }
}
public class Option
{
public string Name { get; set; }
public decimal Price { get; set; }
}
The Action Method looks like:
[HttpPost]
public JsonResult Offerte(Models.Quotation quotation)
{
//
}
The jQuery looks like:
$.post(baseUrl + "/api/Offerte/", jsonContent, function (data) {
alert(data.Message);
});
The jsonContent object looks like:
{
"Options":[
{
"Name":"Extra pagina's (16)",
"Price":40
},
{
"Name":"Papier Keuze",
"Price":30
},
{
"Name":"Omslag",
"Price":29.950000000000003
}
],
"Amount":"5",
"BasePrice":99.96000000000001,
"SubTotal":199.91000000000003,
"SendPrice":0,
"Discount":19.991,
"TotalPrice":179.91900000000004,
"Email":"someone#example.com"
}
Does anyone know why the array is not set properly?
EDIT
If I add this debug code to the controller:
using (var writer = System.IO.File.CreateText(Server.MapPath("~/App_Data/debug.txt")))
{
writer.AutoFlush = true;
foreach (var key in Request.Form.AllKeys)
{
writer.WriteLine(key + ": " + Request.Form[key]);
}
}
Options[0][Name]: Extra pagina's (52)
Options[0][Price]: 156
Options[1][Name]: Papier Keuze
Options[1][Price]: 68.4
Options[2][Name]: Omslag
Options[2][Price]: 41.94
Amount: 6
BasePrice: 149.91899999999998
SubTotal: 416.25899999999996
SendPrice: 0
Discount: 45.78848999999999
TotalPrice: 370.47051
Email: someone#example.com
This means that the data does get to the controller but the Options still doesn't get set right. And I don't want an easy fix that I parse it myself afterwards, I want to know the correct way to handle it so MVC will take care of it.
If you want to send JSON data to an ASP.NET MVC controller action and you want the model binding work currently (e.g. binding collections on your model) you need to specify the contentType as "aplication/json".
Because with the $.post you are not able to specify the contentType you need to use $.ajax and you also need to JSON.stringify your data:
$.ajax({
url: baseUrl + "/api/Offerte/",
type: 'POST',
data: JSON.stringify(jsonContent),
contentType: "application/json",
success: function (data) {
alert(data.Message);
}
});

Categories

Resources