ASP.NET MVC - REST

Author
Discussion

anxious_ant

Original Poster:

2,626 posts

85 months

Wednesday 14th December 2022
quotequote all
I am working on an ASP.NET MVC makes calls to a shipping API via REST. Payload is JSON.

I am fairly new to this and so far have managed to serialise the data and have managed to make a POST call. However I am struggling to deserialise the response. It doesn’t like my class type even though I’ve type casted. Perhaps I have setup my API classes wrongly. I’ve used JSON to C sharp website to generate the response classes.

My GoogleFu is failing me thus far, and I struggle to find a simple to understand tutorial. Hopefully someone here can provide some guidance.

Wheatsheaf

111 posts

74 months

Wednesday 14th December 2022
quotequote all
Feel free to post up code snippets and details of the error if you like. I'd have thought you want something like this:

https://stackoverflow.com/questions/34302845/deser...

If you're having trouble deserializing JSON into instantiations of your classes I guess it's failing on a line similar to this:

Rootobject object = JsonConvert.DeserializeObject<Rootobject>(jsonString);

Can be a bit complicated if it's a collection you're dealing with.

But yeah, I think I'd need a bit more detail to advise further.

anxious_ant

Original Poster:

2,626 posts

85 months

Wednesday 14th December 2022
quotequote all
Wheatsheaf said:
Feel free to post up code snippets and details of the error if you like. I'd have thought you want something like this:

https://stackoverflow.com/questions/34302845/deser...

If you're having trouble deserializing JSON into instantiations of your classes I guess it's failing on a line similar to this:

Rootobject object = JsonConvert.DeserializeObject<Rootobject>(jsonString);

Can be a bit complicated if it's a collection you're dealing with.

But yeah, I think I'd need a bit more detail to advise further.
Thanks for the offer for assistance smile Posting on StackOverflow could be brutal for a beginner like me smile

Yes, you are correct that's the line of code where I am having problems with. Below is my root class. I had to replace curly braces with square braces so I can use the code block here.


public class Root
[
public List<Product> products [ get; set; ]
public List<ExchangeRate> exchangeRates [ get; set; ]
]

public class RootResponse
[
public Root root [ get; set; ]
]


This is the bit of code where the deserialisation is failing (apologies not sure how to post multiple code blocks here)

HttpResponseMessage Res = await client.PostAsJsonAsync(client.BaseAddress, requestBody);

if (Res.IsSuccessStatusCode)
{
var ProductsResponse = await Res.Content.ReadAsStringAsync();
List<RootResponse> products = JsonConvert.DeserializeObject<List<RootResponse>>(ProductsResponse) ;
}


I know the request body is good as I get a 200 statuscode without reserialising.

Mr Happy

5,707 posts

226 months

Wednesday 14th December 2022
quotequote all
Can you post an example of your JSON?

TheGroover

1,018 posts

281 months

Wednesday 14th December 2022
quotequote all
If you're getting the JSON response back I'd copy it, create a new class in your project and then Edit>Paste special>paste JSON as object (I think!)

This will ensure your receiving object is correctly structured.

I'm looking at the list of objects with a quizzical eye. It would usually be an array I would have thought

anxious_ant

Original Poster:

2,626 posts

85 months

Wednesday 14th December 2022
quotequote all
Mr Happy said:
Can you post an example of your JSON?
Of course, please see below example 200 response body.
I pasted that into https://json2csharp.com/ to generate the C# classes. I did create the RootResponse class myself to experiment.

error I am getting in the compiler is : "Cannot deserialize the current JSON object into type 'System.Collections.Generic.List because the type requires a JSON array"

code said:
{
"products": [
{
"productName": "EXPRESS DOMESTIC",
"productCode": "N",
"localProductCode": "N",
"localProductCountryCode": "CZ",
"networkTypeCode": "TD",
"isCustomerAgreement": false,
"weight": {
"volumetric": 0,
"provided": 1.5,
"unitOfMeasurement": "metric"
},
"totalPrice": [
{
"currencyType": "BILLC",
"priceCurrency": "GBP",
"price": 141.51
}
],
"totalPriceBreakdown": [
{
"currencyType": "BILLC",
"priceCurrency": "GBP",
"priceBreakdown": [
{
"typeCode": "SPRQT",
"price": 114.92
}
]
}
],
"detailedPriceBreakdown": [
{
"currencyType": "BILLC",
"priceCurrency": "GBP",
"breakdown": [
{
"name": "12:00 PREMIUM",
"serviceCode": "YK",
"localServiceCode": "YK",
"typeCode": "string",
"serviceTypeCode": "SCH",
"price": 5,
"priceCurrency": "GBP",
"isCustomerAgreement": false,
"isMarketedService": false,
"isBillingServiceIndicator": false,
"priceBreakdown": [
{
"priceType": "TAX",
"typeCode": "All Bu",
"price": 0,
"rate": 0,
"basePrice": 5
}
],
"tariffRateFormula": "((0.3464 % COST) MAX (528.33))"
}
]
}
],
"serviceCodeMutuallyExclusiveGroups": [
{
"serviceCodeRuleName": "Exclusive Billing Services",
"description": "Mutually exclusive Billing Services - shipment can contain just one of following",
"serviceCodes": [
{
"serviceCode": "PZ"
}
]
}
],
"serviceCodeDependencyRuleGroups": [
{
"dependentServiceCode": "PZ",
"dependencyRuleGroup": [
{
"dependencyRuleName": "Labelfree and PLT rule",
"dependencyDescription": "Labelfree requires Paperless Trade (PLT) only if PLT is allowed for product globaly",
"dependencyCondition": "Must provide the requiredServiceCode if it is allowed for the productCode",
"requiredServiceCodes": [
{
"serviceCode": "WY"
}
]
}
]
}
],
"pickupCapabilities": {
"nextBusinessDay": false,
"localCutoffDateAndTime": "2019-09-18T15:00:00",
"GMTCutoffTime": "16:00:00",
"pickupEarliest": "09:30:00",
"pickupLatest": "16:00:00",
"originServiceAreaCode": "ELA",
"originFacilityAreaCode": "HHR",
"pickupAdditionalDays": 0,
"pickupDayOfWeek": 3
},
"deliveryCapabilities": {
"deliveryTypeCode": "QDDC",
"estimatedDeliveryDateAndTime": "2019-09-20T12:00:00",
"destinationServiceAreaCode": "PRG",
"destinationFacilityAreaCode": "PR3",
"deliveryAdditionalDays": 0,
"deliveryDayOfWeek": 5,
"totalTransitDays": 2
},
"items": [
{
"number": 1,
"breakdown": [
{
"name": "DUTY",
"serviceCode": "II",
"localServiceCode": "II",
"typeCode": "DUTY",
"serviceTypeCode": "FEE",
"price": 20,
"priceCurrency": "CZK",
"isCustomerAgreement": false,
"isMarketedService": false,
"isBillingServiceIndicator": false,
"priceBreakdown": [
{
"priceType": "P",
"typeCode": "TAX",
"price": 110,
"rate": 10,
"basePrice": 100
}
],
"tariffRateFormula": "((0.3464 % COST) MAX (528.33))"
}
]
}
],
"pricingDate": "2020-02-25"
}
],
"exchangeRates": [
{
"currentExchangeRate": 1.188411,
"currency": "GBP",
"baseCurrency": "EUR"
}
],
"warnings": [
"Price can't be calculated"
]
}
edit: Is there a way to paste multiple code blocks on PH? smile

Edited by anxious_ant on Wednesday 14th December 20:50

anxious_ant

Original Poster:

2,626 posts

85 months

Wednesday 14th December 2022
quotequote all
TheGroover said:
If you're getting the JSON response back I'd copy it, create a new class in your project and then Edit>Paste special>paste JSON as object (I think!)

This will ensure your receiving object is correctly structured.

I'm looking at the list of objects with a quizzical eye. It would usually be an array I would have thought
I am trying to figure out how to return the response as string object biggrin


Mr Happy

5,707 posts

226 months

Wednesday 14th December 2022
quotequote all
Try changing your deserialize type to Root rather than RootObject.

The JSON you posted doesn't have a named root object, but the Products, ExchangeRates and Warnings objects seem to be valid JSON lists.

As long as your Products and ExchangeRates objects are correct, that should deserialize correctly.

anxious_ant

Original Poster:

2,626 posts

85 months

Wednesday 14th December 2022
quotequote all
Mr Happy said:
Try changing your deserialize type to Root rather than RootObject.

The JSON you posted doesn't have a named root object, but the Products, ExchangeRates and Warnings objects seem to be valid JSON lists.

As long as your Products and ExchangeRates objects are correct, that should deserialize correctly.
Cheers. I think I did try root initially and it failed, hence why I created the new class myself.

Will have a go tomorrow and report back smile

Wheatsheaf

111 posts

74 months

Wednesday 14th December 2022
quotequote all
Here's a code snippet that reads your JSON and displays the product name EXPRESS DOMESTIC.

You definitely had one extra unnecessary level of complexity with your RootResponse. Other than that the deserializer is quite forgiving of various different ways of handling collections. As you can see, in my Root class I have got a mixture of lists and arrays and it works OK - e.g. changing it between public Product[] products{get; set; } and public List<Product> products{get; set; } works fine either way.

I have created very simple versions of your classes with most of the properties missing. That doesn't matter though, the structure of it works and allows you to navigate the data.

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
//This is your JSON
string json = System.IO.File.ReadAllText(@"c:\temp\test.json");
Root response = JsonConvert.DeserializeObject<Root>(json);
//Shows "EXPRESS DOMESTIC"
MessageBox.Show(response.products[0].productName);
}

}
public class Root
{
public List<Product> products{get; set; }
public List<ExchangeRate> exchangeRates { get; set; }
public string[] warnings { get; set; }
}

public class Product
{
public string productName { get; set; }
}
public class ExchangeRate
{
public string currency { get; set; }
}

Edited by Wheatsheaf on Wednesday 14th December 22:42

anxious_ant

Original Poster:

2,626 posts

85 months

Thursday 15th December 2022
quotequote all
Wheatsheaf said:
<snip> Lots of helpful stuff </snip>
Many thanks for this, greatly appreciated.
Collecting a new car this morning so will give this a go when I’m back later smile

For the request body I have a function which serialises the JSON. This function currently returns the body as string but seem to work when I POST to the web API, judging from response status code.

Just a thought, perhaps deserialising is failing due to the response? This is the part I am not too sure about.

Below is snippet of the code that does the api call :

code said:
HttpResponseMessage Res = await client.PostAsJsonAsync(client.BaseAddress, requestBody);

if (Res.IsSuccessStatusCode)
{
var ProductsResponse = await Res.Content.ReadAsStringAsync();
List<RootResponse> products = JsonConvert.DeserializeObject<List<RootResponse>>(ProductsResponse) ;
}
I am building a MVC web app so it’s not as quick to create test forms (or at least not for me wink) I have to create the methods in the controller and call it in the view (page)

Edited by anxious_ant on Thursday 15th December 08:47


Edited by anxious_ant on Thursday 15th December 08:49

Wheatsheaf

111 posts

74 months

Thursday 15th December 2022
quotequote all
Congratulations on the new car smile

If your response from the server is the actual JSON you posted at 20:45 yesterday, then there is nothing wrong with that. You can check it via https://jsonformatter.org/json-viewer.

If you use my classes and my code on that JSON of 20:45 yesterday then it deserializes successfully. I know you say you're locked into an MVC web app but if I were you I'd simplify it down to the absolute basics.... the JSON received is just a string of text so you can easily set up a test in a Console or Windows Forms app just to check the deserialization.

If my classes work and yours don't (which are presumably more complex and numerous) then DM me your source code including your class definitions and I will take a look.

Mr Happy

5,707 posts

226 months

Thursday 15th December 2022
quotequote all
OP: I'd suggest making use of third party tools such as Telerik Fiddler or Postman to test how your API/Controller operates - that way you can post the raw JSON to your method directly, and use the VS debugger to step through the code to see what's going on, rather than have to create/amend custom views each time.

There's also a slightly easier to implement (if slightly harder to debug) way to handle this kind of object creation from JSON - Model Binding. One way of doing this, which I think would benefit you is using the [FromBody] attribute decoration. This shortcuts the "get response, get response body, deserialize response body to object, operate on object" way you've currently got things implemented, but the type you're deserializing into has to be correct (which it appears, as before - yours seem to be).

To use the FromBody attribute, you'd change your method from something like:

[HttpPost]
public void DoSomething()
{
// get response, get response body, deserialize to type etc.
}

to

[HttpPost]
public void DoSomething([FromBody] Root MyObject)
{
// now you'd act on MyObject directly, as the underlying runtime has deserialized it for you into MyObject
}

https://learn.microsoft.com/en-us/aspnet/core/mvc/...

There is an even more trick way of doing this kind of thing using dynamic types and expando objects that doesn't even need classes written to deserialize into, but that's getting somewhat out of the comfort zone I'd think!

Edited by Mr Happy on Thursday 15th December 12:59

anxious_ant

Original Poster:

2,626 posts

85 months

Thursday 15th December 2022
quotequote all
Many thanks Wheatsheaf and Mr Happy for your very useful advice. Running a bit late today from collecting the car so need some time to catch up with work.

Quite a lot to digest so please bear with me while I work my way through your suggestions smile

I shall report back!