I'm following an Udemy .NET 7 API Course and I have this strange behaviour.
Basically I have:
-A WebAPI .NET7
-An MVC Client which has a BaseService class for requesting the API.Also I have a custom class APIResponse.
Thing is, in BaseService
HttpResponseMessage apiResponse = await client.SendAsync(message);
var apiContent=await apiResponse.Content.ReadAsStringAsync();
APIResponse ApiResponse = JsonConvert.DeserializeObject<APIResponse>(apiContent);
If my API returns an Ok() or Ok(someInstance), the Deserializing works perfectly. ApiResponse has
client data in all of its properties (StatusCode,Result,ErrorMessages(as null),and IsSuccess).
But the problem is ... If I return a BadRequest(ModelState), with an ErrorMessage (using ModelState.AddMessageError("key","message"), the deserializing only instances an ApiResponse with the
ErrorMessage property. IsSuccess comes as true (default behaviour), StatusCode as 0 (since it's an int ) and Result as null. This last property it's OK, since it's a bad request.
Hope you can help me. I'll leave some classes below.
APIResponse Model
using System.Net;
namespace ClienteCredencialesMVC.Models
{
public class APIResponse
{
public HttpStatusCode StatusCode { get; set; }
public bool IsSuccess { get; set; } = true;
public List<string> ErrorMessages { get; set; }
public object Result { get; set; }
}
}
I've tried debugging, but I'm not sure how it really the serializing knows how to map a client response to a custom modelClass. That's why I don't understand the BadRequest behaviour.
Related
I am working on Market and Financial News app.I took the API from https://www.marketaux.com/. I am trying to display the news from the site into my home page. I have created a model file and controller in ASP.NET Core web app file. In controller file, I get an error in response.data part as its showing response doesn't have data object whereas when I print the output of response.Content, it has data object. Can you tell me how to solve this and get access to the data from the API so that I can display it on my Home page?
Controller class:
using System.Runtime.CompilerServices;
using Azure.Core;
using MarketNews.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MySql.Data.MySqlClient;
using MySqlX.XDevAPI;
using Newtonsoft.Json;
using RestSharp;
namespace MarketNews.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class NewsController : ControllerBase
{
[HttpGet]
public News[] GetNews()
{
List<News> news = new List<News>();
RestClient client = new RestClient("https://api.marketaux.com/v1/news/all");
// client.Timeout = -1;
RestRequest request = new RestRequest("News",Method.Get);
request.AddQueryParameter("api_token", "qIWtsblpK93oeo23o87egUGBoVmVaqkl4fdHRTEc");
request.AddQueryParameter("symbols", "aapl,amzn");
request.AddQueryParameter("limit", "50");
RestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
if (response != null)
{
foreach (var article in response.data)
{
news.Add(new News
{
Title = article.title,
Description = article.description,
Url = article.url,
Url_Image = article.image_url,
Published_At = article.published_at
});
}
}
return news.ToArray();
}
}
}
Model class
namespace MarketNews.Models
{
public class News
{
public string Title;
public string Description;
public string Url;
public string Url_Image;
public DateTime Published_At;
}
}
I wanted to get data from the API and display it on my Home Page. I used RestClient and RestRequest to get response and then from response. I got the output when response.Content printed to console, it is working and it gave me a json file as output. In foreach loop when I tried to set the response data to the model data I created, it's showing response.data doesn't exist.
I want to know what is wrong here or is there any other method to get the data from the API?
Api: Website link
From RestSharp documentation, the Execute() method does support for a generic type.
Execute<T>(IRestRequest request)
Note that, this method is deprecated. You shall look for the method below:
ExecuteAsync<T>(IRestRequest request, CancellationToken cancellationToken)
and revamp your API action to support asynchronous operation.
From the response data shared in the link, you need a Root object which contains the data property with the List<New> type.
Define the data model class for the response.
using System.Text.Json;
public class Root
{
[JsonPropertyName("data")]
public List<News> Data { get; set; }
}
public class News
{
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
[JsonPropertyName("url")]
public string Url { get; set; }
[JsonPropertyName("image_url")]
public string Url_Image { get; set; }
[JsonPropertyName("published_at")]
public DateTime Published_At { get; set; }
}
For the caller, specify the generic type as Root. Next extract the data property from the response with response.Data.Data.
RestResponse<Root> response = client.Execute<Root>(request);
// asynchronous way
// RestResponse<Root> response = await client.ExecuteAsync<Root>(request);
if (response != null)
news = response.Data.Data;
from my rest call, I am receiving this JSON:
{
"livemode": true,
"error": {
"type": "unauthorized",
"message": "You did not provide a valid API key."
}
}
I need to fetch type and message into my type:
public class TestObject
{
string type { get; set; }
string message { get; set; }
}
But this returns null objects:
HttpClient client = new HttpClient();
Uri uri = new Uri("https://api.onlinebetaalplatform.nl/v1");
HttpResponseMessage response = await client.GetAsync(uri);
string content = await response.Content.ReadAsStringAsync();
JObject json = JObject.Parse(content);
TestObject album = json.ToObject<TestObject>();
1.) I understand that the type and message attributes are "nested". How do I access them?
2.) Even if I call my type livemode and error, the objects still return null.
Can you help me out a little?
Thank you :)
There seems to be one set of curly brackets to many. I am pretty sure that the api you are querying is not returning the first and the last curly bracket. Continue on after that has been taken care of.
In order to fetch the data, add these class definitions
public class Error
{
public string type { get; set; }
public string message { get; set; }
}
public class Root
{
public bool livemode { get; set; }
public Error error { get; set; }
}
and change
TestObject album = json.ToObject<TestObject>();
To
Root album = json.ToObject<Root>();
As some of the comments to your question mentioned, you are currently trying to convert the JSON string to the nested Error object instead of the root object, where the Error object is located.
In the future, there are tools that can generate C# classes from JSON. I used https://json2csharp.com/ this time around to do so.
EDIT:
I just found out that Visual Studio actually has an in-built JSON to Class feature!
I learned how to get information from an API using the microsoft docs, the microsoft docs don't show how to get nested/layers deep objects. The only video I found that showed how to do it did it something like this. However I can't get it to work, receiving only an error stating this:
"Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Dashboard.Weather' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly."
Any help is appreciated, i'm trying to get the "Weather.description", here's my sample code:
public class Weather
{
public string Description{ get; set; }
}
public class Product
{
public Weather Weather { get; set; }
}
public static class ApiHelper
{
static string city_id = "CITY_ID";
static string api_key = "API_KEY";
public static HttpClient client = new HttpClient();
public static void InitializeClient()
{
client.BaseAddress = new Uri($"http://api.openweathermap.org/data/2.5/weather?id={city_id}&APPID={api_key}");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public static async Task<Weather> GetProductAsync()
{
Product product = null;
HttpResponseMessage response = await client.GetAsync("");
if (response.IsSuccessStatusCode)
{
product = await response.Content.ReadAsAsync<Product>();
}
return product.Weather;
}
}
async void SetLabelText()
{
var weather = await ApiHelper.GetProductAsync();
descriptionLabel.Text = $"Description: {weather.Description}";
}
The response from the API is formatted as follows
{"coord":{"lon":-89.59,"lat":41.56},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"base":"stations","main":{"temp":279.27,"feels_like":275.4,"temp_min":278.15,"temp_max":280.37,"pressure":1027,"humidity":74},"visibility":16093,"wind":{"speed":3.1,"deg":200},"clouds":{"all":1},"dt":1576951484,"sys":{"type":1,"id":3561,"country":"US","sunrise":1576934493,"sunset":1576967469},"timezone":-21600,"id":4915397,"name":"Walnut","cod":200}
Your Product model does not correctly align with the json you are receiving.
The json you've post has weather as a list, but Product assumes it will just be an object. The json parser, then, correctly fails when seeing it is an array in the actual json instead of a JSON object.
The fix should be simple; Product.Weather should be of type List<Weather> (or IEnumerable<Weather> or Weather[], whichever fits your needs).
I'm trying to get a specific value from my content, but I don't have any idea how I can do it.
I'm using RestSharp(C#) to run my JSON code, but when I execute the command it returns an error. I need to get the value from the property errorMessage.
var json = JsonConvert.SerializeObject(objectToUpdate);
var request = new RestRequest(Method.POST);
request.RequestFormat = DataFormat.Json;
request.AddCookie("authToken", token);
request.AddParameter("application/json", json, ParameterType.RequestBody);
var response = client.Execute<T>(request);
After execute this code my response return the below JSON:
{
"response":{},
"status":{
"success":false,
"detail":{
"errormessage":[{
"fieldName":"departmentCode",
"errorMessage":"Department code provided is already associated with another department",
"errorCode":"DUPLICATE_DEPARTMENT_CODE"
}],
"error":"Validation failure on request",
"operation":"internal",
"errorcode":"412"
}
}
}
You could have a class, that represents the response you expect:
class ApiResponse{
// use a class that represents normal response instead of object
// if you need to interact with it.
public object Response {get; set;}
public ResponseStatus Status{get; set;}
}
class ResponseStatus {
public StatusDetail Detail{get; set;}
public bool Success {get; set;}
}
class StatusDetail {
public ErrorMessage[] ErrorMessage{get; set;}
}
class ErrorMessage{
public string FieldName{get; set;}
public string ErrorMessage{get; set;}
public string ErrorCode{get; set;}
}
Then you have that, you can get parsed response from RestSharp client:
var response = client.Execute<ApiResponse>(request);
var message = response.Data.Response.Detail.ErrorMessage.First().ErrorMessage;
According to RestSharp docs, they recomment using response classes over dynamic objects (https://github.com/restsharp/RestSharp/wiki/Recommended-Usage).
I am trying to call a rest api method from c#. Problem is for all content types it passes null to body parameter.I shared my code below.Apart from this code I have tried to write body parameter to request as stream.It didn't work either. I have also tried 'application/x-www-form-urlencoded' as content type.
Calling rest api method from c# sample:
string token = Server.UrlEncode("v0WE/49uN1/voNwVA1Mb0MiMrMHjFunE2KgH3keKlIqei3b77BzTmsk9OIREken1hO9guP3qd4ipCBQeBO4jiQ==");
string url = "http://localhost:2323/api/Applications/StartProcess?token=" + token;
string data = #"{""ProcessParameters"": [{ ""Name"":""flowStarter"",""Value"": ""Waffles"" }],
""Process"": ""RESTAPISUB""}";
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.BaseAddress = new System.Uri(url);
byte[] cred = UTF8Encoding.UTF8.GetBytes("username:password");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(cred));
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
System.Net.Http.HttpContent content = new StringContent(data, UTF8Encoding.UTF8, "application/json");
HttpResponseMessage messge = client.PostAsync(url, content).Result;
string description = string.Empty;
if (messge.IsSuccessStatusCode)
{
string result = messge.Content.ReadAsStringAsync().Result;
description = result;
}
Rest api Method:
[HttpPost]
[ActionName("StartProcess")]
public int StartProcess([FromUri]string token,[FromBody]WorkflowStartParameters parameters)
{
try
{
LoginInformation info = CasheProcesses.ReadCashe(token);
eBAWSAPI api = Service.GetWSService();
WorkflowProcess proc = api.StartProcess(info.Id, info.Password, info.ImpersonateUserId, info.Language, parameters);
return proc.ProcessId;
}
catch (Exception ex)
{
throw new Exception("An error occured when starting process,exception detail:" + ex);
}
}
WorkflowStartParameters class structure:
public class WorkflowStartParameters
{
public WorkflowParameter[] ProcessParameters;
public string Process { get; set; }
}
public class WorkflowParameter
{
public string Name { get; set; }
public string Value { get; set; }
}
I have searched this problem a lot. It seems as a very common problem. I just found this solution working properly, passing request parameter to rest api method and reading body parameter from there. But it is not a valid solution for me.
If you have any idea,feel free to share.
Thanks,
Zehra
I don´t know if it can solve your problem, but let me try.
I guess you don´t have to utilize Server.UrlEncode in your call, but:
Dim myUri As New Uri(Token)
And I guess you must not encode also your username and password - try pass them as string.
Your problem appear to be here:
public class WorkflowStartParameters
{
public WorkflowParameter[] ProcessParameters; <---- needs get/set
public string Process { get; set; }
}
This needs to be a public property to serialize properly. Currently you have it set up as a public field. Just add { get; set; } and give that a try. I would also look into serializing with Newtonsoft.Json to ensure your object is properly serialized. Trying to do it with escape strings will be messing the more data you are sending.
By the way there can be issues sometimes serializing arrays, I would change that to :
public List<WorkflowParameter> ProcessParameters{get;set;}
Finally I have achieved to send filled out data to server. It was about serialization problem. But it didn't work with json serialization before send data. I have added DataContract attribute to my class and it works properly.
Unfortunately still I couldn't figure out this when I make ajax calls from java script it works without DataContract attribute but if I call it in c# it needs DataContract attribute. If someone share the information about this I would appreciate!
I am sharing new class structure, everything else but this still same:
[Serializable]
[DataContract]
public class WorkflowParameter
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string Value { get; set; }
}
[Serializable]
[DataContract]
public class WorkflowStartParameters
{
[DataMember]
public WorkflowParameter[] ProcessParameters { get; set; }
[DataMember]
public string Process { get; set; }
}