400 Bad Request submitting Json to WebApi via HttpClient.PutAsync - c#

Normally, serialized objects would be used from the services to the webapi calls but in this instance I have to use a json representation for the call.
The process would be to deserialize the json to the proper class, then process as usual.
HttpClient Put
Method is called from within a console app
public async Task<ApiMessage<string>> PutAsync(Uri baseEndpoint, string relativePath, Dictionary<string, string> headerInfo, string json)
{
HttpClient httpClient = new HttpClient();
if (headerInfo != null)
{
foreach (KeyValuePair<string, string> _header in headerInfo)
_httpClient.DefaultRequestHeaders.Add(_header.Key, _header.Value);
}
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json-patch+json"));
var content = new StringContent(json, Encoding.UTF8, "application/json-patch+json");
var response = await httpClient.PutAsync(CreateRequestUri(relativePath, baseEndpoint), content);
var data = await response.Content.ReadAsStringAsync();
...
}
Endpoint
The call never hits the endpoint. The endpoint is hit if I remove the [FromBody] tag but as expected, the parameter is null. There seems to be some sort of filtering happening.
[HttpPut()]
[Route("")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UpdatePaymentSync([FromBody] string paymentSyncJson)
{
if (string.IsNullOrEmpty(paymentSyncJson))
return BadRequest();
//hack: don't have access to models so need to send json rep
var paymentSync = JsonConvert.DeserializeObject<PaymentSync>(paymentSyncJson);
....
}
This is the json payload. I thought [FromBody] took care of simple types but this is proving me wrong.
{
"paymentSyncJson": {
"id": 10002,
"fileName": "Empty_20190101.csv",
"comments": "Empty File",
"processingDate": "2019-01-02T19:43:11.373",
"status": "E",
"createdDate": "2019-01-02T19:43:11.373",
"createdBy": "DAME",
"modifiedDate": null,
"modifiedBy": null,
"paymentSyncDetails": []
}
}

Just expanding on my Comment.
The OP did:
[HttpPut()]
[Route("")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UpdatePaymentSync([FromBody] string paymentSyncJson)
{
if (string.IsNullOrEmpty(paymentSyncJson))
return BadRequest();
//hack: don't have access to models so need to send json rep
var paymentSync = JsonConvert.DeserializeObject<PaymentSync>(paymentSyncJson);
....
}
Where they have put [FromBody] string paymentSyncJson, FromBody will try and deserialise into the type you specify, in this case string. I suggest doing:
public async Task<IActionResult> UpdatePaymentSync([FromBody] JObject paymentSyncJson)
Then you can change this line:
var paymentSync = JsonConvert.DeserializeObject<PaymentSync>(paymentSyncJson);
To:
var paymentSync = paymentSyncJson.ToObject<PaymentSync>();

Your payload is not a string, it's a json, that's why the runtime can't parse the body to your requested string paymentSyncJson.
To solve it, create a matching dto which reflects the json
public class PaymentDto
{
public PaymentSyncDto PaymentSyncJson { get; set; }
}
public class PaymentSyncDto
{
public int Id { get; set; }
public string FileName { get; set; }
public string Comments { get; set; }
public DateTime ProcessingDate { get; set; }
public string Status { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime ModifiedDate { get; set; }
public string ModifiedBy { get; set; }
public int[] PaymentSyncDetails { get; set; }
}
Then use it in the controller method to read the data from the request body
public async Task<IActionResult> UpdatePaymentSync([FromBody] PaymentDto payment)

Related

How to Post into a third party URL and get access token

In my ASP.NET Core-6 Web API, I have a challenge. I have a 3rd party URL as given below:
URL: http://api.thirdpartycompany.com:2233/api/oauth/login
Request:
{
"username": to_be_shared,
"password": to_be_shared
}
Response:
‘{
"response_code": "00",
"response_description": "Success"
"data": {...},
"size": 0,
"access_token": "access_token",
"refresh_token": "refresh_token",
"expires_in": "18000000",
"token_type": "BEARER"
}’
Sample Access Token Request Call:
url = "http://api.thirdpartycompany.com:2233/api/oauth/login"
payload = '{
"username": to_be_shared,
"password": to_be_shared
}'
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data = payload)
I am to POST into that given url of the third party and get their response, including the access token.
So far, I have done this:
DTO:
public class OAuthLoginRequest
{
public string username { get; set; }
public string password { get; set; }
}
public class OAuthLoginResponse
{
public string response_code { get; set; }
public string response_description { get; set; }
public int size { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public string expires_in { get; set; }
public string token_type { get; set; }
}
So far, this is what I have:
public class GenericResponseDto<T>
{
public string response_code { get; set; }
public string response_description { get; set; }
}
public interface IAuthService
{
Task<GenericResponseDto<object>> LoginUser(OAuthLoginRequest request);
}
public class AuthService : IAuthService
{
public async Task<GenericResponseDto<object>> LoginUser(OAuthLoginRequest request)
{
var response = new GenericResponseDto<object>();
return response;
}
}
DIServiceExtension:
public static class DIServiceExtension
{
public static void AddDependencyInjection(this IServiceCollection services)
{
// Add Service Injections Here -- Auth
services.AddScoped<IAuthService, AuthService>();
}
}
Program.cs
builder.Services.AddDependencyInjection();
Using HttpClient, How do I complete AuthService and when user enters the required username and password, submits the request then it gets the response including access token from the third party URL (http://api.thirdpartycompany.com:2233/api/oauth/login) as response?
How do I achieve this?
FWIW please use either JsonPropertyNameAttribute or JsonProperty depending on what serialization/ deserialization library you're using and keep the names of the properties in your C# classes C#-like (i.e PascalCase, not snake_case)
The answer is written assuming you did that, so I'll use the ResponseCode property instead of response_code
Now to the actual answer.
This is relatively simple, you just need to post your data to the third part API, read the result and return it up the call stack. Like so
public class AuthService : IAuthService
{
// Configure your HttpClient injection as described here:
// https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
private readonly HttpClient _httpClient;
public AuthService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<OAuthLoginResponse> LoginUser(OAuthLoginRequest request)
{
var postContent = new StringContent(
JsonSerializer.Serialize(request),
Encoding.UTF8,
"application/json"
);
var response = await _httpClient.PostAsync("oauth/login", postContent);
if (!response.IsSuccessStatusCode)
throw new Exception("Third party API request failed");
await using var stream = await response.Content.ReadAsStreamAsync();
return JsonSerializer.DeserializeAsync<OAuthLoginResponse>(stream);
}
}
Instead of returning a OAuthLoginResponse directly, you could change your GenericResponseDto first of to something specific like OAuthResponseDto and secondly to have the fields you need, like
public class OAuthResponseDto
{
[JsonPropertyName("response_code")]
public string ResponseCode { get; set; }
[JsonPropertyName("response_description")]
public string ResponseDescription { get; set; }
// More properties, like AccessToken, RefreshToken, ExpiresIn...
}
And then replace the last two lines of the LoginUser with this:
await using var stream = await response.Content.ReadAsStreamAsync();
var response = JsonSerializer.DeserializeAsync<OAuthLoginResponse>(stream);
return new OAuthResponseDto
{
ResponseCode = response.ResponseCode,
ResponseDescription = response.ResponseDescription,
// rest of the mapping
};
Small aside, this is how you'd configure your HttpClient injection
services.AddHttpClient<IAuthService, AuthService>(client =>
{
client.BaseAddress = new Uri("http://api.thirdpartycompany.com:2233/api");
});
Note: I intentionally left out quite a bit of error checking to make this answer short and sweet, but it's still quite a long answer, you'll have to add that error checking in your implementation.
Note 2: I also don't 100% guarantee that all of this compiles first go, I did my best, but I wrote a large part of this in the Stack Overflow editor, there may be a missing parenthesis or comma or semicolon somewhere, trivial fixes

POST Request of a JSON Array

I am able to write a controller for posting 1 object to the database in my controller. However I want to now post multiple objects in 1 API call using JSON
My Entity
public class CustomerInvoiceLine: BaseEntity
{
public SchoolFee SchoolFee { get; set; }
public int SchoolFeeId { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
public int Amount { get; set; }
public int CustomerInvoiceId { get; set; }
}
I have a DTO as follows:
public class CustomerInvoiceLineAddDto
{
public int SchoolFeeId { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
public int Amount { get; set; }
public int CustomerInvoiceId { get; set; }
}
I have created a Repository:
public async Task AddCustomerInvoiceLineAsync(CustomerInvoiceLine customerInvoiceLine)
{
_context.CustomerInvoiceLines.Add(customerInvoiceLine);
await _context.SaveChangesAsync();
}
And finally the Controller
[HttpPost]
public async Task<ActionResult> AddCustomerInvoiceLine(CustomerInvoiceLineAddDto invoiceLineAddDto)
{
CustomerInvoiceLine invoiceLineDetails = new CustomerInvoiceLine();
_mapper.Map(invoiceLineAddDto, invoiceLineDetails);
await _unitOfWork.CustomerInvoiceLineRepository.AddCustomerInvoiceLineAsync(invoiceLineDetails);
return Ok();
}
The above controller works fine for posting just 1 item in the JSON request.
I then tried to change this to receive a JSON Array.
public async Task<ActionResult> AddCustomerInvoiceLine(CustomerInvoiceLineAddDto invoiceLineAddDto)
{
string json = "";
CustomerInvoiceLine invoiceLineDetails = new CustomerInvoiceLine();
CustomerInvoiceLineAddDto invoiceLines = JsonConvert.DeserializeObject<CustomerInvoiceLineAddDto>( json );
_mapper.Map(invoiceLineAddDto, invoiceLines);
await _unitOfWork.CustomerInvoiceLineRepository.AddCustomerInvoiceLineAsync(invoiceLineDetails);
return Ok();
}
My JSON Request:
[
{
"schoolFeeId": 1,
"customerInvoiceId": 18,
"description": "School Fees for 2022",
"quantity": 1,
"amount": 5000
},
{
"schoolFeeId": 1,
"customerInvoiceId": 18,
"description": "School Fees for 2021",
"quantity": 1,
"amount": 3000
}
]
Could somebody please assist with understanding how to deserialise the JSON body and then process to the database?
If you want to accept an array, make sure the controller accepts an array. Then automapper is smart enough to convert an array of one type to an array of another type.
public async Task<ActionResult> AddCustomerInvoiceLine(CustomerInvoiceLineAddDto[] invoiceLinesAddDto)
{
var invoiceLineDetails = _mapper.Map<List<CustomerInvoiceLine>>(invoiceLinesAddDto);
await _unitOfWork.CustomerInvoiceLineRepository.AddCustomerInvoiceLinesAsync(invoiceLineDetails);
return Ok();
}
In your unit of work, add a function to handle the adding of a list
public async Task AddCustomerInvoiceLinesAsync(IEnumerable<CustomerInvoiceLine> customerInvoiceLines)
{
_context.CustomerInvoiceLines.AddRange(customerInvoiceLines);
await _context.SaveChangesAsync();
}
Be advised, you can't make a single controller method to handle both the array request and the single item request. I would suggest to define two distinct endpoint so consumers of your api knows which needs an array and which one not.

Add Method returns null empty object to database

I've got data from an API, deserializing it to work with my model, but when I try to add that data to my database its only stores a blank object.I've tried passing through CrdResponse but that provides an exception that I can't get around.
public async Task <ActionResult> InsertCard(Card card)
{
List<Card> CardsInfo = new List<Card>();
var getUrl = System.Web.HttpContext.Current.Request.Url.AbsoluteUri;
var test = new Uri(getUrl);
var id = test.Segments.Last();
SingleResult += id;
using (var client = new HttpClient())
{
//passing service baseurl
client.BaseAddress = new Uri(SingleResult);
client.DefaultRequestHeaders.Clear();
//Define request data format
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Sending request to find web api REST service resource Getallcards using HTTPClient
HttpResponseMessage Res = await client.GetAsync(SingleResult);
//Checking the response is successful or not which is sent using HttpClient
if (Res.IsSuccessStatusCode)
{
//Storing the response details received from web api
var CrdResponse = Res.Content.ReadAsStringAsync().Result;
//Deserializing the response recieved from web api and storing in to the card list
CardsInfo = JsonConvert.DeserializeObject<List<Card>>(CrdResponse);
_context.Cards.Add(card);
_context.SaveChanges();
}
}
return RedirectToAction("Index", "API");
}
and my Model
public class Card
{
public int Id { get; set; }
public string Name { get; set; }
public int? Atk { get; set; }
public int? Def { get; set; }
public string Desc {get; set;}
public int? Level { get; set; }
public string Type { get; set; }
public string Attribute { get; set; }
[DisplayName("Image")]
public IList<Image> Card_Images { get; set; }
public IList<Deck> Deck { get; set; }
}
EDIT: CrdResponse contains:
[{"id":"11714098","name":"30,000-Year White Turtle","type":"Normal Monster","desc":"A huge turtle that has existed for more than 30,000 years.","atk":"1250","def":"2100","level":"5","race":"Aqua","attribute":"WATER","card_images":[{"id":"11714098","image_url":"https://storage.googleapis.com/ygoprodeck.com/pics/11714098.jpg","image_url_small":"https://storage.googleapis.com/ygoprodeck.com/pics_small/11714098.jpg"}],"card_prices":{"cardmarket_price":"0.00","tcgplayer_price":"0.00","ebay_price":"10.0","amazon_price":"0.00"}}]
which is all the info i am hoping to pull through
You are adding the 'Card' you pass through in the method parameter:
public async Task <ActionResult> InsertCard(Card card)
...
_context.Cards.Add(card);
I think what you want to do is pass through the card you are deserialising from the string response:
CardsInfo = JsonConvert.DeserializeObject<List<Card>>(CrdResponse);
foreach(Card c in CardsInfo)
{
_context.Cards.Add(c);
}
You forgot to await the result of the ReadAsStringAsync method, like so:
var CrdResponse = await Res.Content.ReadAsStringAsync();
As it is an async method it might not have completed the operation when you call result, that depends on the size of the string you are trying to read.
Hope to have helped!

HttpClient ObjectContent JSON: format with root class name in json object?

I generate a C# class for a json source using json2csharp.com. My json is:
{
"email_verified": true,
"user_id": "gg2323",
"app_metadata": {
"tc_app_user": {
"user_guid": "c0fb150f6er344df98ea3a06114e1e4a",
"cto_admin_a_user_id": "551294d4f6cfb46e65a5aq71",
"lang": "EN",
"country": "USA",
"disabled": false
}
and my resulting C# is:
public class TcAppUser
{
public string user_guid { get; set; }
public string cto_admin_a_user_id { get; set; }
public string lang { get; set; }
public string country { get; set; }
public bool disabled { get; set; }
}
public class AppMetadata
{
public TcAppUser tc_app_user { get; set; }
public int logins_count { get; set; }
}
public class RootObject
{
public bool email_verified { get; set; }
public string user_id { get; set; }
public AppMetadata app_metadata { get; set; }
}
Using the .NET HttpClient GET, I can read into this C# structure from the JSON API quite nicely. Going the other way (POST, PATCH) poses a problem: my app_metadata property name is dropped in the generated JSON output when I use a common approach like:
//Would be nice: var contentIn = new ObjectContent<string>(RootObjectInstance.app_metadata, new JsonMediaTypeFormatter());
string json = JsonConvert.SerializeObject(RootObjectInstance.app_metadata);
HttpResponseMessage response = await hclient.PatchAsync("api/users/" + user_id, new StringContent(json, Encoding.UTF8, "application/json"));
The resulting JSON is now:
{
"tc_app_user": {
"lang": "en-en",
"country": "GER",
"disabled": false
}
}
My quick hack is to use the following additional wrapper to dynamically repackage the app_metadata property so it has the same format going out that it had coming in. The rest remains the same as above:
dynamic wireFormatFix = new ExpandoObject();
wireFormatFix.app_metadata = usr.app_metadata;
string json = JsonConvert.SerializeObject(wireFormatFix);
Now my JSON output corresponds to the JSON input. My question: what is best-practice to achieve symmetric json input and output here without a pesky format fix?
EDIT: If I try to PATCH the entire structure (RootObjectInstance instead of RootObjecInstance.app_metadata) I get:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Payload validation error: 'Additional properties not allowed: 'user_id'."
}
So, I must either send the app_metadata subset/property of the C# RootObject, properly packaged, or I must selectively delete fields from the RootObject to meet the API's requirements.
Thanks!
The root app_metadata tag is being removed from your JSON because you're simply not serializing it. This:
string json = JsonConvert.SerializeObject(RootObjectInstance.app_metadata);
Will serialize everything that is inside app_metadata.
If you serialized the entire object graph, you wouldn't need to patch anything:
string json = JsonConvert.SerializeObject(RootObjectInstance);
As a side note, you should follow C# naming conventions. You can use JsonProperty to help you with that.
Edit:
Ok, after your edit i see the actual problem. You're calling an API by user_id in your query string, and you also have a user_id property inside your object. This seems like you need two different objects for the job.
You have a couple of possibilities:
Create an object hierarchy:
public class BaseObject
{
[JsonProperty(email_verified)]
public bool EmailVerified { get; set; }
[JsonProperty(app_metadata)]
public AppMetadata AppMetadata { get; set; }
}
public class ExtendedObject : BaseObject
{
[JsonProperty(user_id)]
public string UserId { get; set; }
}
And then use the base type to serialize the data:
var baseObj = new BaseObject(); // Fill the object properties.
var json = JsonConvert.SerializeObject(intermidiateObj);
HttpResponseMessage response = await hclient.PatchAsync("api/users/" +
user_id,
new StringContent(json,
Encoding.UTF8,
"application/json"));
Use an anonymous object which includes only properties you actually need:
var intermidiateObj = new { app_metadata = usr.app_metadata };
var json = JsonConvert.SerializeObject(intermidiateObj);
HttpResponseMessage response = await hclient.PatchAsync("api/users/" +
user_id,
new StringContent(json,
Encoding.UTF8,
"application/json"));

How to get value from JSON Dictionary using Newtonsoft.Json

I'm new in json and I have a little problem here that I can't solve.
In other json I have {"certificate":"123456789",} and I get the value of that using.
model.cs
class Login //this code is working.
{
[JsonProperty("certificate")]
public string certificate { get; set; }
}
But my problem is when I come up with json dictionary I don't have any Idea how can i get the value from it.
this is my code from controller.cs to get the json from Saba/api/component/location
using(var client = new HttpClient()) //I'm sure this code is working.
{
client.BaseAddress = new Uri(string.Format("https://{0}/", HostUrl));
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("SabaCertificate", certificate);
//HTTP GET: /Saba/api/componet/location
HttpResponseMessage response = await client.GetAsync("Saba/api/component/location");
if(response.IsSuccessStatusCode)
{
Location saba = await response.Content.ReadAsAsync<Location>();
//I always get empty location when I try to run the app.
}
}
This is JSON with dictionary.
{
"results": [
{
"loc_name": "Aberdeen - Aberdeen Town Square_489",
"id": "locat000000000001877",
"href": "https://na1.sabacloud.com/Saba/api/component/location/locat000000000001877"
}]
}
How can I get the value of json dictionary?
What I've tried is
Model.cs
[JsonProperty("results")] \\ AggregateException Error
class Location
{
[JsonProperty("loc_name")]
public string loc_name { get; set; }
[JsonProperty("id")]
public string id { get; set; }
[JsonProperty("href")]
public string href { get; set; }
}
But I can't retrieve the value.
Based on the JSON posted in your question you should have something like
public class Result
{
public string loc_name { get; set; }
public string id { get; set; }
public string href { get; set; }
}
public class RootObject
{
public List<Result> results { get; set; }
}
then just call
var rootObject = JsonConvert.DeserializeObject<RootObject>(jsonData);
You can always use JSON Formatter & Validator to test if your JSON is valid, and json2csharp to generate c# classes from json.

Categories