I have been tasked with building an API, that as a request, will take a product number which will have a quantity and size , zip code, shipping method.
The customer has a cart and in that cart is the product number, quantity and size
so basically he would send a json request that looks like the below
{
"ShoppingCart": {
"Products": [
{
"Sku": "123",
"Size": "S",
"Quantity": "1"
},
{
"Sku": "456",
"Size": "M",
"Quantity": "2"
},
{
"Sku": "789",
"Size": "L",
"Quantity": "3"
}
],
"ShipToZip": "54452",
"ShipMethod": "Ground"
}
}
is it possible to receive an HTTP json request on my .net core rest webapi that im making.
If so, what would the route look like to send json like that? it'sgoing to be pretty long if they have to put the entire json in the url right?
EDIT:
After doing more research, I find that I can receive a POST request with JSON in the body, from there i should be able to read that json, do some stuff with it, and then return json back right? Am i correct?
After doing more research, I find that I can receive a POST request with JSON in the body, from there i should be able to read that json, do some stuff with it, and then return json back right? Am i correct?
Yes. You are correct. For instance, the following controller action would accept a POST body with the JSON from your question and respond with that same JSON.
public class Product
{
public string Sku { get; set; }
public string Size { get; set; }
public int Quantity { get; set; }
}
public class Cart
{
public List<Product> Product { get; set; }
public string ShipToZip { get; set; }
public string ShipMethod { get; set; }
}
public class CartBody
{
public Cart Cart { get; set; }
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// POST api/values
[HttpPost]
public ActionResult<CartBody> Post(CartBody cartBody)
{
return cartBody;
}
}
I guess I did not look hard enough :(
Anyway the more questions I feel like the more exposure some newbie problems will have.
How to receive json in ASP.NET web api?
I got my solution from there,
All I had to do was create a "Model" class like below that matches the exact JSON format being sent, no deserielization needed
public class RequestModel
{
public ShoppingCart RequestShoppingCart { get; set; }
}
public class ShoppingCart
{
public Products[] Products { get; set; }
public int ShipToZip { get; set; }
public string ShipMethod { get; set; }
}
public class Products
{
public string Sku { get; set; }
public string Size { get; set; }
public int Quantity { get; set; }
}
Then from there in my API Controller i can do the following to see it working
[Produces("application/json")]
[Route("api/ShippingCalculator")]
public class ShippingCalculatorController : Controller
{
// POST: api/ShippingCalculator
[HttpPost]
public string Post([FromBody]RequestModel jsonRequest)
{
// Debug.WriteLine(jsonRequest.RequestShoppingCart.Products);
return jsonRequest.RequestShoppingCart.ShipMethod;
}
}
Related
The data in JSON format is sent to api endpoint.
Data sample:
{
"templateId": "dc15e4d1-ccbd-4581-a819-5b7f90b32cc5",
"name": "abc",
"steps": [
{
"id": "34b4f406-120e-4d80-8018-6c780c80a6c4",
"visible": false,
}
]
}
Api gets data in this format:
public class TemplateRequest {
public Guid TemplateId { get; set; }
public string Name { get; set; }
public StepRequest[] Steps { get; set; }
}
StepRequest class:
public class StepRequest {
[ModelBinder(Name = "id")]
public Guid StepId { get; set; }
public bool? Visible { get; set; }
}
The JSON has id key instead of stepId, but I can't get it in controller.
When I check, the StepId is always an empty Guid.
What is wrong here, why the StepId property is not having the value from id key?
Yes, like #daremachine said, it was NewtonSoft and JsonProperty(PropertyName = "id") helped.
I am developing an endpoint in C# to accept JSON posted from an external provider (Telnyx). Here is a sample of the data:
{
"data": {
"event_type": "fax.received",
"id": "e15c28d4-147e-420b-a638-2a2647315577",
"occurred_at": "2021-11-19T16:37:02.863682Z",
"payload": {
"call_duration_secs": 35,
"connection_id": "1771912871052051547",
"direction": "inbound",
"fax_id": "2a168c93-3db5-424b-a408-b70a3da625bc",
"from": "+12399999999",
"media_url": "https://s3.amazonaws.com/faxes-prod/999",
"page_count": 1,
"partial_content": false,
"status": "received",
"to": "+12399999999",
"user_id": "dc6e79fa-fe3b-462b-b3a7-5fb7b3111b8a"
},
"record_type": "event"
},
"meta": {
"attempt": 1,
"delivered_to": "https://webhook.site/27ef892c-c371-4976-ae22-22deea57080e"
}
}
I have verified this is valid JSON through https://jsonlint.com/. I created a model:
public class myDeserializedClass
{
public class Payload
{
public int call_duration_secs { get; set; }
public string connection_id { get; set; }
public string direction { get; set; }
public string fax_id { get; set; }
public string from { get; set; }
public string media_url { get; set; }
public int page_count { get; set; }
public bool? partial_content { get; set; }
public string status { get; set; }
public string to { get; set; }
public string user_id { get; set; }
}
public class Data
{
public string event_type { get; set; }
public string id { get; set; }
public DateTime occurred_at { get; set; }
public Payload payload { get; set; }
public string record_type { get; set; }
}
public class Meta
{
public int attempt { get; set; }
public string delivered_to { get; set; }
}
public class Root
{
public Data data { get; set; }
public Meta meta { get; set; }
}
}
The controller being posted to looks like:
[HttpPost]
public IActionResult InboundFax(myDeserializedClass json)
{
try
{
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(json.ToString().Trim());
return Content("OK");
}
catch(Exception ex)
{
return Content(ex.ToString());
}
}
I am receiving the error: Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: K. Path '', line 0, position 0. each time the API tries to post to my endpoint. I have also tried posting data using Postman and receive the same error message. Additionally, there are examples of JSON posting on the API website at https://developers.telnyx.com/docs/v2/programmable-fax/tutorials/receive-a-fax-via-api. Since my application fails with both postman and real-time API calls, I'm am working on the assumption the problem is my code, but can't be 100% certain and don't know how to fix it. This is a mission critical problem that I need to solve. Any help would be appreciated.
First of all, the class is bad. Should be:
public class Payload
{
public int call_duration_secs { get; set; }
public string connection_id { get; set; }
public string direction { get; set; }
public string fax_id { get; set; }
public string from { get; set; }
public string media_url { get; set; }
public int page_count { get; set; }
public bool? partial_content { get; set; }
public string status { get; set; }
public string to { get; set; }
public string user_id { get; set; }
}
public class Data
{
public string event_type { get; set; }
public string id { get; set; }
public DateTime occurred_at { get; set; }
public Payload payload { get; set; }
public string record_type { get; set; }
}
public class Meta
{
public int attempt { get; set; }
public string delivered_to { get; set; }
}
public class myDeserializedClass
{
public Data data { get; set; }
public Meta meta { get; set; }
}
It depends on the data that you are getting, but if you are getting the object, you don't need to convert it to work:
[HttpPost]
public IActionResult InboundFax(myDeserializedClass json)
{
try
{
//Work directly with json as object, forget "root" is: myDeserializedClass
return Content("OK");
}
catch(Exception ex)
{
return Content(ex.ToString());
}
}
or if you are getting the json as string:
[HttpPost]
public IActionResult InboundFax(string json)
{
try
{
//Work directly with json as object
myDeserializedClass myInstance= JsonConvert.DeserializeObject<myDeserializedClass>(json);
return Content("OK");
}
catch(Exception ex)
{
return Content(ex.ToString());
}
}
UPDATE AFTER TESTING IT:
I test it with a dummy controller:
[HttpPost]
public ActionResult InboundFax(myDeserializedClass json)
{
try
{
//Just dummy test
if (json.meta.attempt == 1)
{
return Content("OK");
}
else {
return Content("NO");
}
//Work directly with json as object, forget "root" is: myDeserializedClass
}
catch (Exception ex)
{
return Content(ex.ToString());
}
}
in a HomeController (blank template from MVC Web just dummy)
So posting to:
https://localhost:44334/Home/InboundFax
METHOD: POST
With the following data:
{
"data": {
"event_type": "fax.received",
"id": "e15c28d4-147e-420b-a638-2a2647315577",
"occurred_at": "2021-11-19T16:37:02.863682Z",
"payload": {
"call_duration_secs": 35,
"connection_id": "1771912871052051547",
"direction": "inbound",
"fax_id": "2a168c93-3db5-424b-a408-b70a3da625bc",
"from": "+12399999999",
"media_url": "https://s3.amazonaws.com/faxes-prod/999",
"page_count": 1,
"partial_content": false,
"status": "received",
"to": "+12399999999",
"user_id": "dc6e79fa-fe3b-462b-b3a7-5fb7b3111b8a"
},
"record_type": "event"
},
"meta": {
"attempt": 1,
"delivered_to": "https://webhook.site/27ef892c-c371-4976-ae22-22deea57080e"
}
}
Little quickwatch you see it map everything:
Could mean the POSTMAN is wrong configurated?
I use the following header:
Content-Type: application/json
I'm using TALEND API TESTER for Chrome, but every REST client is similar
With POSTMAN, same result, OK. Check for body: raw, type JSON, and header with the content type applicantion/json
Well, I am not sure if I have an answer or not, however, I did manage to get the application working by changing the endpoint to a WebApi instead of a MVC controller. I was under the impression a MVC controller could accept json data, however, I was unable to ever get it working. Once I changed it, everyting worked perfectly.
I am working with an ASP.NET Core 3.1x API that is using the Microsoft.AspNetCore.Odata v7.4.0 NuGet, and I have an ASP.NET Core 3.1v web client that is using the Simple.OData.V4.Client v5.12.0
I am trying to Post an Order object with related LineItem objects (a master/detail) from the Simple.Odata client. The Order object is being passed to the API correctly but the related LineItem collection is not.
Here is my Order class;
public class Order
{
public Guid Id { get; set; }
public DateTimeOffset DateOfOrder { get; set; }
public string PurchaseOrderNumber { get; set; }
public decimal Subtotal { get; set; }
public decimal Discount { get; set; }
public decimal Shipping { get; set; }
public decimal SalesTax { get; set; }
public decimal Total { get; set; }
public List<OrderLineItem> OrderLineItems { get; set; } = new List<OrderLineItem>();
}
Here is my OrderLineItem class;
public class OrderLineItem : BaseModel
{
public Guid Id { get; set; }
public Guid OrderId { get; set; }
public Order Order { get; set; }
public Guid ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Discount { get; set; }
public decimal Total { get; set; }
}
Here is the method in my ODataService of the client website application;
public async Task<Result<Order>> CreateOrderAsync(Order req, CancellationToken cancellationToken)
{
try
{
var baseUrl = cdUtilities.GetBaseApiUrl();
var accessToken = await GetAccessToken();
var client = new ODataClient(SetODataToken(baseUrl, accessToken));
var response = await client.For<Order>()
.Set(req).InsertEntryAsync(cancellationToken);
return Result.Ok(response);
}
catch (WebRequestException badRequest)
{
var odataErrorResponse = JsonConvert.DeserializeObject<ExceptionResponse>(badRequest.Response);
var errorMessage = $"Bad Request: The API returned an HTTP Status Code 400.{Environment.NewLine}";
if (odataErrorResponse.Error != null)
{
foreach (var errorDetail in odataErrorResponse.Error.Details)
{
errorMessage = $"{errorMessage} {errorDetail.Message}{Environment.NewLine}";
}
}
logger.LogError(badRequest, errorMessage);
return Result.Fail<Order>(errorMessage);
}
catch (Exception ex)
{
logger.LogError(ex, ex.Message);
return Result.Fail<Order>(ex.Message);
}
}
Here is the json of the Order object and the related OrderLineItem objects I am using in Postman that works correctly with my API.
{
"#odata.context": "https://localhost:44367/v1/$metadata#Orders",
"DateOfOrder": "2020-05-14T08:00:43.511Z",
"PurchaseOrderNumber": "P051420200001",
"Subtotal": 450.00,
"Discount": 0.00,
"Shipping": 3.45,
"SalesTax": 28.00,
"Total": 495.89,
"OrderLineItems": [
{
"ProductId": "BF43F1C7-6796-4C92-B4DD-08D7DBE4BBCC",
"ProductName": "Test Product 2",
"Quantity": 6,
"UnitPrice": 53.00,
"Discount": 0.00,
"Total": 318.00,
"Length": 10.0,
"Width": 4.0,
"Height": 7.0,
"Weight": 0.302,
"DimUnit": "Inch",
"WeightUnit": "Pound",
"Status": "Active",
"DeleteFlag": false
}
]
}
When I post a new Order with related OrderLineItems using Postman, My API creates the Order and the related OrderLineItem records just fine. However, when I post using the Simple.OData.V4.Client the Order record gets created but not the related OrderLineItem records.
When I look at the OData API controller when Posting the record via Postman, I can see that the Order object being passed in with the included OrderLineItems.
However, when I look at the OData API controller when using the Simple.OData.V4.Client from my web application, I see that the OrderLineItems has a count of 0, so the Simple OData client is not sending the related OrderLineItem records, even though I can see them in the Order object that is passed into the CreatreOrderAsync method in the OData service of the web application.
So, I have to assume that I am missing something needed by Simple OData client to allow it to include the related records but after reviewing the Simple OData Client documentation, I cannot find an example of a master/detail type client Post method.
What am I missing here?
***** UPDATE 05/14/20 *****
Simple.Odata.Client does not support deep linking (models with realted data in a single operation). Microsoft has an OData client that is being actively updated and it does support related data. It is more complex but also seems to be more flexible (ie ODataClientFactory and DI support). Here is a link to the documentation.
The Microsoft client reminds me of what we had to do in WCF, in that we set up a proxy using Connected Services and link to the APIs meta data in Visual Studio. I do like that they support LINQ queries and I like the IOdataClientFactory so that we can unit test without an actual network call.
After a user fills out a form, I need post the form contents back in JSON and get a chunk of HTML back for display. This seems like a good case for razor pages. The BrandTemplateInfo parameter on the OnPost handler is always null. I can't seem to get the BrandTemplateInfo to populate from the model binder. What am I missing here? This used to be easy with MVC controllers. What am I missing here? Help?
public class PayNowCardModel : PageModel
{
public void OnGet()
{
}
public IActionResult OnPost([FromBody] BrandTemplateInfo brandTemplateInfo)
{
return Page();
}
public BrandTemplateInfo BrandTemplateInfo { get; set; }
}
HTTP Request:
POST /terms/paynowcard HTTP/1.1
Host: xxxxxxxx
Content-Type: application/json
Cache-Control: no-cache
{
"userForm": {
"cardNumber": "4111111111111111",
"paymentAmount": 123.33
},
"account": {
"Creditor": {
"Name": "big time creditor"
},
"accountId": "32432432432423"
}
}
You should make sure the BrandTemplateInfo object has the same name properties as the posted json property :
public class BrandTemplateInfo
{
public userForm userForm { get; set; }
public account account { get; set; }
}
public class account
{
public Creditor Creditor { get; set; }
public string accountId { get; set; }
}
public class Creditor
{
public string Name { get; set; }
}
public class userForm
{
public string cardNumber { get; set; }
public float paymentAmount { get; set; }
}
So that model binding would work with [FromBody] attribute .
I am currently trying to progress with EF Core with a one-to-many (a user has many items). A tutorial or three later I managed to get things working with two very small and simple tables; however, I got a json exception: A possible object cycle was detected which is not supported which indicated that I had circular references.
Here is my code that gets around the issue using DTO objects, but is there a more cleaner way I can get around this issue as typing the, though it works, felt a bit wrong.
User:
namespace TestWebApplication.Database
{
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
public ICollection<Items> Items { get; set; }
}
}
Items:
namespace TestWebApplication.Database
{
public class Items
{
[Key]
public int ItemId { get; set; }
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public virtual User User { get; set; }
}
}
DtoItems:
namespace TestWebApplication.Database.DTOs
{
public class DtoItems
{
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public DtoUser User { get; set; }
}
}
DtoUser:
namespace TestWebApplication.Database.DTOs
{
public class DtoUser
{
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
}
}
TestController:
[HttpGet]
[Route("getitems")]
public ActionResult<List<Items>> GetItems()
{
List<Items> items = _myContext.Items.Include(i => i.User).ToList();
// DTOs
List<DtoItems> dtoItems = new List<DtoItems>();
foreach (var i in items)
{
var dtoItem = new DtoItems
{
Item = i.Item,
Category = i.Category,
Type = i.Type,
User = new DtoUser
{
UserName = i.User.UserName,
Dob = i.User.Dob,
Location = i.User.Location
}
};
dtoItems.Add(dtoItem);
}
return Ok(dtoItems);
}
The output from endpoint:
[
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xx"
}
},
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xxx"
}
}
]
In my opinion, the use of a DTO is the correct way of dealing with this issue. The fact that your datamodel does not serialize well is trying to hint to you that you should not be serializing your datamodel from the API at all.
I think returning a DTO also solves further issues down the road (What if you want to return all properties of the UserModel except one, maybe it's a sensitive property you don't want to just return from your API, what if your UserModel in the DB gets more navigation properties that you don't want to return?).
There is really only two other ways of handling this.
You can switch to Newtonsoft.Json which has support for handling reference loops and you can configure it one single line
Like so :
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
System.Text.Json does not have support for doing this (yet). Follow this Github Issue for more info : https://github.com/dotnet/runtime/issues/30820
You use the JsonIgnore attribute to force non serialization of properties which will work but... It looks weird to have an EntityFramework model have JSON Serialization options on it...
So your best bet, stick with the DTO.
More info :
https://dotnetcoretutorials.com/2020/03/15/fixing-json-self-referencing-loop-exceptions/
https://github.com/dotnet/runtime/issues/30820
https://www.newtonsoft.com/json/help/html/ReferenceLoopHandlingIgnore.htm