I am creating a UWP app, simply put I have an async void GetWeather function that reads JSON from an API and creates an object. Calling the Forecast.getWeather() function I get the error " object reference required from non-static method". I have done my research but have not found a solution to this with a void method as I don't want to return an object just yet. Also if this is not possible (and maybe a better idea) how would I return the Object so it can be used on many different pages throughout the app or would the object values still be accessible if created in the void method?
Forecast.cs
class Forecast
{
public async void GetWeather()
{
var uri = new Uri("MY API URI HERE");
using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage response = await client.GetAsync(uri))
{
using (IHttpContent content = response.Content)
{
var json = await content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RootObject>(json);
Debug.WriteLine("In async method");
}
}
}
}
}
MainPage
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
Forecast.GetWeather();
}
}
Weather.cs
namespace WeatherForecast
{
public class Main
{
public double temp { get; set; }
public double temp_min { get; set; }
public double temp_max { get; set; }
public double pressure { get; set; }
public double sea_level { get; set; }
public double grnd_level { get; set; }
public int humidity { get; set; }
public double temp_kf { get; set; }
}
public class Weather
{
public int id { get; set; }
public string main { get; set; }
public string description { get; set; }
public string icon { get; set; }
}
public class Clouds
{
public int all { get; set; }
}
public class Wind
{
public double speed { get; set; }
public double deg { get; set; }
}
public class Snow
{
public double __invalid_name__3h { get; set; }
}
public class Sys
{
public string pod { get; set; }
}
public class List
{
public int dt { get; set; }
public Main main { get; set; }
public List<Weather> weather { get; set; }
public Clouds clouds { get; set; }
public Wind wind { get; set; }
public Snow snow { get; set; }
public Sys sys { get; set; }
public string dt_txt { get; set; }
}
public class Coord
{
public double lat { get; set; }
public double lon { get; set; }
}
public class City
{
public int id { get; set; }
public string name { get; set; }
public Coord coord { get; set; }
public string country { get; set; }
}
public class RootObject
{
public string cod { get; set; }
public double message { get; set; }
public int cnt { get; set; }
public List<List> list { get; set; }
public City city { get; set; }
}
}
The problem is that your method is not static, so you need to create an instance of the Forecast class to access it. You can read more on this in this SO question and also here.
The quick solution also would be to make your Forecast class static (as long as you don't want to have multiple different instances of it):
static class Forecast
{
public static async void GetWeather()
{
var uri = new Uri("MY API URI HERE");
using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage response = await client.GetAsync(uri))
{
using (IHttpContent content = response.Content)
{
var json = await content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RootObject>(json);
Debug.WriteLine("In async method");
}
}
}
}
}
But now we come upon a bigger problem. Your method is async and is async void. This is something you should do only when absolutely necessary as async void methods are so-called fire-and-forget. They start, but when they reach the first true asynchronous call, they will execute on their own and if something bad happens inside the method (like an exception) you will never know about it until the symptoms start appearing elsewhere in a hard-to-debug way. Also you never know when the result is actually ready for you to use.
The best solution is then Task return type. This presents a promise that the result will be available when the method execution is finished.
static class Forecast
{
public static RootObject Result {get; private set;}
public static async Task GetWeatherAsync()
{
var uri = new Uri("MY API URI HERE");
using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage response = await client.GetAsync(uri))
{
using (IHttpContent content = response.Content)
{
var json = await content.ReadAsStringAsync();
Result = JsonConvert.DeserializeObject<RootObject>(json);
Debug.WriteLine("In async method");
}
}
}
}
}
You can see the method is no longer void but it still does not return the RootObject directly (as you requested, otherwise you could use Task<RootObject> to return it). I also added a Result property so that the result is accessible when the execution is finished. Because the class is static, the property is accessible from anywhere after the GetWeatherAsync method call finished.
Now how can you use this? Instead of the constructor you can call the method in OnNavigatedTo handler:
public override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo( e );
try
{
await Forecast.GetWeatherAsync();
//after await finishes, Result is ready
//do something with Forecast.Result
}
catch
{
//handle any exceptions
}
}
You may notice that I violated the rule that async methods should not be void. However, in this case, we are dealing with event handler which has a fixed notation so you have no other choice. However, I have added a try-catch block so that we can handle any exceptions if they happen.
I recommend reading more on async-await in documentation as it will help you understand the concept much better. I also suggest you to check out more detailed info on instance vs. static to better understand the distinction and when to use each of them.
Related
I'm really confused right now.
I'm not sure how to implement correctly Currency Exchange from one Currency to another using asp.net core, using this API: Currency Exchange-API
What I'm trying to do is when user input Salary it should exchange that value from foreign currency in EUR and USD currency.
This provided API is very weird, I mean I don't have much experience with API's. With SDK's yes but with API's unfortunately not much.
So here is my Code so hopefully someone will know what I need to do to Implement this API right.
using System.ComponentModel.DataAnnotations;
using System.Security.AccessControl;
namespace Test_Project_Web.Models
{
public class EmployeeCategory
{
[Key]
public int Id { get; set; }
[Required]
public double GrossSalary_ForeignCurrency { get; set; }
[Required]
public double NetSalary_EUR { get; set; }
public double NetSalary_USD { get; set; }
using Newtonsoft.Json;
namespace Test_Project_Web.Models
{
public class ExchangeRate_API
{
class Rates
{
public static bool Import()
{
try
{
String URLString = "https://v6.exchangerate-api.com/v6/YOUR-API-KEY/latest/USD";
using (var webClient = new System.Net.WebClient())
{
var json = webClient.DownloadString(URLString);
API_Obj Test = JsonConvert.DeserializeObject<API_Obj>(json);
return true;
}
}
catch (Exception)
{
return false;
}
}
}
public class API_Obj
{
public string ? result { get; set; }
public string ? documentation { get; set; }
public string ? terms_of_use { get; set; }
public string ? time_last_update_unix { get; set; }
public string ? time_last_update_utc { get; set; }
public string ? time_next_update_unix { get; set; }
public string ? time_next_update_utc { get; set; }
public string ? base_code { get; set; }
public ConversionRate ? conversion_rates { get; set; }
}
public class ConversionRate
{
public double EUR { get; set; }
public double USD { get; set; }
}
}
}
using Microsoft.AspNetCore.Mvc;
using Test_Project_Web.Data;
using Test_Project_Web.Models;
namespace Test_Project_Web.Controllers
{
public class EmployeeCategoryController : Controller
{
private readonly ApplicationDbContext _db;
public EmployeeCategoryController(ApplicationDbContext db)
{
_db = db;
}
public IActionResult Index()
{
IEnumerable<EmployeeCategory> objEmployeeCategoryList = _db.EmployeeCategories;
return View(objEmployeeCategoryList);
}
//GET
public IActionResult Create()
{
return View();
}
//POST
[HttpPost]
[AutoValidateAntiforgeryToken]
public IActionResult Create(EmployeeCategory obj)
{
if (ModelState.IsValid)
{
_db.EmployeeCategories.Add(obj);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(obj);
}
}
}
To use the data from the Currency Exchange API, you could wrap the API call in a service provider:
public class ExchangeRateProvider
{
public ConversionRate? Rate { get; private set; }
private const string ApiUrl = "https://v6.exchangerate-api.com/v6/<API-KEY>/latest/{0}";
public ExchangeRateProvider()
{
}
public async Task UpdateRatesAsync(string foreignCurrency = "USD")
{
try
{
using var httpClient = new HttpClient();
API_Obj? jsonResponse = await httpClient.GetFromJsonAsync<API_Obj>(string.Format(ApiUrl, foreignCurrency));
Rate = jsonResponse?.conversion_rates;
}
catch(Exception ex)
{
throw new Exception("Unable to download Exchange API data.", ex);
}
}
public class API_Obj
{
public string? result { get; set; }
public string? documentation { get; set; }
public string? terms_of_use { get; set; }
public long? time_last_update_unix { get; set; }
public string? time_last_update_utc { get; set; }
public long? time_next_update_unix { get; set; }
public string? time_next_update_utc { get; set; }
public string? base_code { get; set; }
public ConversionRate? conversion_rates { get; set; }
}
public class ConversionRate
{
public double EUR { get; set; }
public double USD { get; set; }
}
}
To use this provider, you first set up the dependency injection in the Program.cs or Startup.cs,
builder.Services.AddRazorPages();
builder.Services.AddSingleton<ExchangeRateProvider>();
Now, inject this service into your controller or other controllers where you want to use it,
public EmployeeCategoryController(ApplicationDbContext db, ExchangeRateProvider exchangeRateProvider)
{
_db = db;
_exchangeRateProvider = exchangeRateProvider;
}
The provider can be called like this,
public async Task<IActionResult> Create(EmployeeCategory obj)
{
//your other code
var foreignCurrency = "GBP";
await _exchangeProvider.UpdateRatesAsync(foreignCurrency);
var rates = _exchangeProvider.Rate;
var grossSalary = 100; //salary in GBP
var usdSalary = rates.USD * grossSalary;
var eurSalary = rates.EUR * grossSalary;
//check for null or do some calculation with this.
}
I hope this gives you some ideas. There are many other ways to do this depending on your project requirements, e.g. do you want to cache the rate or do you need it always to be the latest? my code is just a POC.
Refactored POC Code:
To use the data from the Currency Exchange API, you could wrap the API call in a service provider:
public class ExchangeRateProvider
{
public ConversionRate? Rate { get; private set; }
private const string ApiUrl = "https://v6.exchangerate-api.com/v6/<API-KEY>/latest/GBP"; //Currency you want to exchange salary in this example GBP. Otherwise Will return 404 error if you put ".../latest/{0}"
public ExchangeRateProvider()
{
}
public async Task UpdateRatesAsync(string foreignCurrency = "USD", string foreignCurrency_02 = "EUR") //Update Rates for USD & EUR
{
try
{
using var httpClient = new HttpClient();
API_Obj? jsonResponse = await httpClient.GetFromJsonAsync<API_Obj>(string.Format(ApiUrl, foreignCurrency, foreignCurrency_02));
Rate = jsonResponse?.conversion_rates;
}
catch(Exception ex)
{
throw new Exception("Unable to download Exchange API data.", ex);
}
}
public class API_Obj
{
public string? result { get; set; }
public string? documentation { get; set; }
public string? terms_of_use { get; set; }
public long? time_last_update_unix { get; set; }
public string? time_last_update_utc { get; set; }
public long? time_next_update_unix { get; set; }
public string? time_next_update_utc { get; set; }
public string? base_code { get; set; }
public ConversionRate? conversion_rates { get; set; }
}
public class ConversionRate
{
public double EUR { get; set; }
public double USD { get; set; }
}
}
The provider can be called like this,
public async Task<IActionResult> Create(EmployeeCategory obj)
{
if(ModelState.IsValid)
{
//your other code
var foreignCurrency = "EUR";
var foreignCurrency_02 = "USD";
await _exchangeRateProvider.UpdateRatesAsync(foreignCurrency);
await _exchangeRateProvider.UpdateRatesAsync(foreignCurrency_02);
var rates = _exchangeRateProvider.Rate;
var grossSalary = GrossSalary_GBP; //salary in GBP
var usdSalary = rates.USD * grossSalary;
var eurSalary = rates.EUR * grossSalary;
obj.NetSalary_USD = eurSalary;
obj.NetSalary_EUR = usdSalary;
obj.NetSalary_GBP = GrossSalary_GBP;
_db.EmployeeCategories.Add(obj);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(obj);
}
(Note: I am a beginner!)
I am doing an ASP.NET Core Website and have to show the list of all countries with their travel advisory scores in a table.
I can't display the data using a Travel Advisory API. Each country is a separate key and are different classes when I paste the JSON as classes.
How can I call each country's class (eg. AU) and display the data under the country using a loop? The problem is that there are too many countries so I hope to use a loop for this. As you can see in the code below, I can only show one country at a time as each country is a different class.
Basic API endpoint: https://www.travel-advisory.info/api
Optional parameter: https://www.travel-advisory.info/api?countrycode=AU (restricts the result list to a single country)
My TravelAdvisory controller's action method:
public async Task<ActionResult> Index()
{
string countryCode = "AU"; //I plan to have multiple country codes in a loop,
//just haven't done so yet as I have issues showing the data for all country codes
// Make Web API call to get a list of travel advisories
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://www.travel-advisory.info");
HttpResponseMessage response = await client.GetAsync("/api");
if (countryCode != null)
{
response = await client.GetAsync("/api?countrycode=" + countryCode);
}
if (response.IsSuccessStatusCode)
{
string data = await response.Content.ReadAsStringAsync();
Rootobject model = JsonConvert.DeserializeObject<Rootobject>(data);
float score = model.data.AU.advisory.score;
//here, I can only get the score for one country since class names are different.
List<float> dataList = new List<float>();
dataList.Add(score);
TempData["Score"] = Convert.ToString(score);
return View();
}
else
{
return View(new List<Data>());
}
}
TravelAdvisory model classes:
public class Rootobject
{
public Api_Status api_status { get; set; }
public Data data { get; set; }
}
public class Api_Status
{
public Request request { get; set; }
public Reply reply { get; set; }
}
public class Request
{
public string item { get; set; }
}
public class Reply
{
public string cache { get; set; }
public int code { get; set; }
public string status { get; set; }
public string note { get; set; }
public int count { get; set; }
}
public class Data
{
public AD AD { get; set; }
public AE AE { get; set; }
public AF AF { get; set; }
public AG AG { get; set; }
public AI AI { get; set; }
public AL AL { get; set; }
public AM AM { get; set; }
...
My code:
Model: https://controlc.com/da48a2be
Controller: https://controlc.com/51d938c7
You need to add a parameter to the Index method like this and write your logic accordingly.
public async Task<ActionResult> Index(string countryCode)
{
And you can use the code in if and else, you you want to get records for all countires if countryCode is null or emotry string.
HttpResponseMessage response = null;
if (string.IsNullOrEmpty(countryCode))
{
response = await client.GetAsync("/api);
}
else
{
response = await client.GetAsync("/api?countrycode=" + countryCode);
}
Edit:
You can use reflection to get all the properties and proceed further.
Data data = ...;
foreach (var prop in data.GetType().GetProperties())
{
...
}
I am trying to access a simple REST Api, https://api.cryptonator.com/api/ticker/btc-usd
The result of the same is like this format:
{"ticker":{"base":"BTC","target":"USD","price":"9969.76308171","volume":"127575.47420967","change":"-197.36472278"},"timestamp":1517410741,"success":true,"error":""}
Now, when I am trying to get result from it, I find ticker objet of json null, timestamp and error objects are getting filled.
So, I suspect there might be the problem datamembers are not matching with json text. My Modeldto looks like this:
public class CurtoUsd
{
public ticker tick { get; set; }
public Single timestamp { get; set; }
public bool success { get; set; }
public string error { get; set; }
}
public class ticker
{
public string _base { get; set; }
public string target { get; set; }
public string price { get; set; }
public string volume { get; set; }
public string change { get; set; }
}
Please have a look, I was suppose to use base as variable but it is the keyword, so instead i used _base.
And I am using httpclient to in asp.net core 2.0 webapi and the code looks like this:
HttpClient client = new HttpClient();
if (client.BaseAddress == null)
{
client.BaseAddress = new Uri("https://api.cryptonator.com/");
}
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync(baseUrl);
CurtoUsd usdrate = new CurtoUsd();
if (response.IsSuccessStatusCode)
{
usdrate = await response.Content.ReadAsJsonAsync<CurtoUsd>();
}
return CommonFunctions.ConvertDouble(usdrate.tick.price);
Detail
of function:
public static class HttpContentExtensions
{
public static async Task<T> ReadAsJsonAsync<T>(this HttpContent content)
{
string json = await content.ReadAsStringAsync();
T value = JsonConvert.DeserializeObject<T>(json);
return value;
}
}
May I know what's wrong I am doing? Is the point I have pointed out correct, is there any solution if it is so.
Please help.
You can use # in C# to use keywords as identifiers, e.g. foo.#base.#class.
So your DTO becomes:
public class Ticker
{
public string #base { get; set; } // Escape the `base` keyword with `#`
public string target { get; set; }
public string price { get; set; }
public string volume { get; set; }
public string change { get; set; }
}
Note that you should use PascalCase for type identifiers n C#/.NET (so class Ticker instead of class ticker), and for public members too.
Note that Newtonsoft.Json is actually case-insensitive for member names by default (unless you specifically configure it otherwise) so you can use PascalCase for the properties, this also stops them from being C# keywords too, and it will still work:
public class Ticker
{
public string Base { get; set; } // `Base` is not a keyword
public string Target { get; set; }
public string Price { get; set; }
public string Volume { get; set; }
public string Change { get; set; }
}
I am new to stack overflow and I am a beginner programmer.
I wanted to make an application that is about League of Legends but I have a problem. I just can't figure out how to deserialize the json into an object.
This is what I've tried
public class LOL
{
public User user { get; set; }
}
public class User
{
public int id { get; set; }
public string name { get; set; }
public int profileIconId { get; set; }
public long revisionDate { get; set; }
public int summonerLevel { get; set; }
}
These are the classes I created using the website http://json2csharp.com/ .
I've just changed the name of these classes to something I like
This is my other class where I call the API
public class LOLFacade
{
private const string APIKey = "secret :D";
public async static Task<LOL> ConnectToRiot(string user)
{
var http = new HttpClient();
string riotURL = String.Format("https://eune.api.pvp.net/api/lol/eune/v1.4/summoner/by-name/{0}?api_key={1}", user, APIKey);
var response = await http.GetAsync(riotURL);
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<LOL>(result);
}
}
This is the place I use the API
private async void GetUserInfo_Click(object sender, RoutedEventArgs e)
{
LOL user = await LOLFacade.ConnectToRiot("gigaxel");
string name = user.user.name;
int level = user.user.summonerLevel;
InfoTextBlock.Text = name + " is level " + level;
}
And I always get this error when I execute my code :( :
Object reference not set to an instance of an object.
Here is the Json:
{"gigaxel": {
"id": 36588106,
"name": "Gigaxel",
"profileIconId": 713,
"revisionDate": 1451494377000,
"summonerLevel": 30
}}
Please if anyone can help me I would really appreciate it. I just started and I think that I made a lot of mistakes in this code but if you could help me I would really appreciate it :D
Your LOL model doesn't match with JSON that you received.
Look my answer in this question: Deserialize JSON into Object C#
The correct model is that
public class LOL
{
public Gigaxel gigaxel { get; set; }
}
public class Gigaxel
{
public int id { get; set; }
public string name { get; set; }
public int profileIconId { get; set; }
public long revisionDate { get; set; }
public int summonerLevel { get; set; }
}
try the following class instead of your LOL class
public class LOL
{
public User gigaxel { get; set; }
}
Apart from the issues pointed by the other users, your error is due to the fact that you are trying to deserialize the JSON into an object that you never instantiated. You have to create the object before deserializing:
LOL LOLObj = new LOL { user = new User() };
return JsonConvert.DeserializeObject<LOLObj>(result);
{"balances-and-info":{"on_hold":[],"available": {"USD":0.93033384},"usd_volume":"243.18","fee_bracket": {"maker":"0.00","taker":"0.60"},"global_usd_volume":"0.09942900"}}
I have this JSON response, and I'm trying to store it in an object, however as you can see "balances-and-info" cannot be used as a variable name. The method I have been using is:
RestClient client = new RestClient("http://currency-api.appspot.com/api/");
RestRequest request = new RestRequest(url);
var response = client.Execute<Currency>(request);
Currency obj = response.Data;
Where obviously the class is a lot easier
public class Currency
{
public string rate { get; set; }
}
So how can I handle this?
String.replace() balances-and-info with balances_and_info
in your code
YourObject deserialized = parseResponse(obj.replace("balances-and-info", "balances_and_info"));
YourObject parseResponse(string response) {
try
{
// https://www.nuget.org/packages/Newtonsoft.Json/
// Json.NET
YourObject ret = JsonConvert.DeserializeObject<YourObject>(response);
return ret;
}
catch (JsonSerializationException)
{
// do something
}
return null;
}
YourObject
Use http://json2csharp.com/ and generate your object (copy response string, replace balances-and-info with balances_and_info and generate)
public class Available
{
public double USD { get; set; }
}
public class FeeBracket
{
public string maker { get; set; }
public string taker { get; set; }
}
public class BalancesAndInfo
{
public List<object> on_hold { get; set; }
public Available available { get; set; }
public string usd_volume { get; set; }
public FeeBracket fee_bracket { get; set; }
public string global_usd_volume { get; set; }
}
public class YourObject
{
public BalancesAndInfo balances_and_info { get; set; }
}