Unable to implement API in ASP.NET Core - c#

(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())
{
...
}

Related

List of objects and image is not being passed (Registered) with FromForm POST Call

i have a model class of the following..
public class RegisterDTO
{
public string Name{ get; set; }
public string Location { get; set; }
public string PhoneNumber { get; set; }
public IFormFile Image { get; set; }
public List<string> Days { get; set; }
//here is the list of costs
public List<CostDTO> Costs { get; set; }
public IFormFile Image1 { get; set; }
public IFormFile Image2 { get; set; }
}
and here is the CostDTO class
public class CostDTO
{
public string itemName { get; set; }
public bool isPercent { get; set; }
public float value { get; set; }
}
and here is the service for registering the values stated above..
public async Task <string> Register(RegisterDTO model)
{
var entity = await _context.Tablename.FirstOrDefaultAsync();
List<Cost> costs = new();
//here is the loop that maps the DTO to an entity and stores it into the list
foreach (var item in model.costs)
{
var cost = _mapper.Map<Cost>(item);
costs.Add(cost);
}
//then save to the database
}
And here is the controller that calls the service.
[HttpPost("info")]
public async Task<ActionResult> Register([FromForm] RegisterDTO model)
{
var result = await _serviceName.Register(model);
return Ok(result);
}
My question is, at the beginning of the for loop, model.costs is empty and its not allowing me to pass a list of objects onto the service call. how do i go about fixing this.
you can fix it like this costs[0].itemName="item1",costs[1].itemName="item 2",
but it's not good solution, it's better to separate your dto ,get images in a model use [fromform] and another model [frombody]
As Hossein said, you should firstly make sure the request has sent the Costs list to the web api controller method.
The asp.net core model binding will auto map the formdata array with the list. So you should use it like below:
Then the result:

C# ASP.NET MVC can not pass 2 objects in TempData from one controller to another controller

I have code in a C# application that uses JSON.SerializeObject to store data in TempData in one controller and then I use JsonConvert.DeserializeObject in a second controller. This is the code in the first controller:
TempData["participantData"] = JsonConvert.SerializeObject(participantData);
return RedirectToAction("Index", "MainPage");
In the above code, I serialize an object called participantData. participantData is defined like this:
ParticipantLoadData participantData = new ParticipantLoadData();
The ParticipantLoadData class looks like this:
public class CurrentParticipant
{
public string comp_first_day_of_year { get; set; }
public string comp_rate_of_return { get; set; }
public string ss_LastName { get; set; }
public string ss_FirstName { get; set; }
public string ss_SocSec { get; set; }
public string ss_Bal { get; set; }
public string ss_Pending { get; set; }
public string ss_LoanOB { get; set; }
public string ss_VstBal { get; set; }
public string ss_ValDate { get; set; }
public int ss_LoanFund { get; set; }
public string fld_CY_IEXP { get; set; }
public string fld_Salary { get; set; }
public string th_last_contribution { get; set; }
public string th_last_contribution_date { get; set; }
}
This all works fine as I am able to retrieve TempData["participantData"] from within the MainPage controller.
When I try to send a second object to TempData, I end up generating a "This site can't be reached" error. This is what I added:
I tried to add a second object to the TempData. I created a List where the Fund Class looks like this:
public class Fund
{
public int FundNumber { get; set; }
public string FundName { get; set; }
}
Then, I populated a list of type Fund with this code:
List<Fund> FundList = new List<Fund>();
var webRoot = _env.WebRootPath + "/txt";
var FILENAME = System.IO.Path.Combine(webRoot, "fund_list.txt");
{
foreach (string line in System.IO.File.ReadLines(FILENAME))
{
var oneFund = new Fund();
oneFund.FundNumber = int.Parse(line.Substring(1, 4));
if (oneFund.FundNumber > 0)
{
oneFund.FundName = FundNameException(line.Substring(6, line.Length - 6));
FundList.Add(oneFund);
}
}
}
This code loads 2000 elements into the FundList.
When this loop completes, I execute these lines of code:
TempData["FundList"] = JsonConvert.SerializeObject(FundList, Formatting.Indented);
TempData["participantData"] = JsonConvert.SerializeObject(participantData);
return RedirectToAction("Index", "MainPage");
I have a break point set at the return, which I hit. When I look at the TempData["FundList"], it appears to contain the correct data in the correct format. I also have a break point set in controller MainPage on the first line of code within the ActionResult Index() which never gets hit.
Any idea why adding this second object to TempData would be causing an issue?
Any insight is greatly appreciated.

How to call REST api result json having c# keywords

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; }
}

Azure Mobile App - error with Entity Framework attempting to re-Insert child - when using $expand

I am looking at Azure Mobile App and have setup a test service and client.
I've setup the following Entities service-side:
public class Hotel : EntityData
{
public string Title { get; set; }
public virtual ICollection<Booking> Bookings { get; set; }
}
public class Booking : EntityData
{
public BookingStatus BookingStatus { get; set; }
[ForeignKey("PersonId")]
public virtual Person Person { get; set; }
[ForeignKey("HotelId")]
public virtual Hotel Hotel { get; set; }
public string PersonId { get; set; }
public string HotelId { get; set; }
}
public class Person : EntityData
{
public string Name { get; set; }
public virtual ICollection<Booking> Bookings { get; set; }
}
And the controller:
public class BookingController : TableController<Booking>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<Booking>(context, Request);
}
// GET tables/Booking/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Booking> GetBooking(string id)
{
return Lookup(id);
}
// GET tables/Booking
public IQueryable<Booking> GetAllBookings()
{
return Query();
}
// PATCH tables/Booking/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Booking> PatchBooking(string id, Delta<Booking> patch)
{
return UpdateAsync(id, patch);
}
}
I have added some default data using CreateDatabaseIfNotExists<MobileServiceContext> and when I startup and test the Web API, the DB gets populated and I am happy that the Keys/Relationships are setup correctly. I am just using the Code First convention naming (as per this tutorial)
I have also created a test client with the following Entities:
public class Person
{
public string Id { get; set; }
public byte[] Version { get; set; }
public string Name { get; set; }
public virtual ICollection<Booking> Bookings { get; set; }
}
public class Booking
{
public string Id { get; set; }
public byte[] Version { get; set; }
public BookingStatus BookingStatus { get; set; }
public string PersonId { get; set; }
public string HotelId { get; set; }
public virtual Person Person { get; set; }
public virtual Hotel Hotel { get; set; }
}
public class Hotel
{
public string Id { get; set; }
public byte[] Version { get; set; }
public string Title { get; set; }
public virtual ICollection<Booking> Bookings { get; set; }
}
And with this test logic:
using (var client = new MobileServiceClient(m_Url, new ODataParameterHandler())
{
client.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
client.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var bookingTable = client.GetTable<Booking>();
var bookings = await placementTable
.Where(p => p.BookingStatus == BookingStatus.Confirmed && p.PersonId == 10)
.WithParameters(new Dictionary<string, string> { { "expand", "Hotel" } })
.ToListAsync();
var aBooking = bookings[0];
aBooking.BookingStatus = BookingStatus.Cancelled;
await bookingTable.UpdateAsync(aBooking);
}
// Class to allow $expand= querystring value to be passed in.
public class ODataParameterHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
UriBuilder builder = new UriBuilder(request.RequestUri);
builder.Query = builder.Query
.Replace("expand", "$expand")
.TrimStart('?');
request.RequestUri = builder.Uri;
return await base.SendAsync(request, cancellationToken);
}
}
The GET/ToListAsync works ok and I get the child Hotel object attached to my Booking. However, the Update fails with:
The operation failed due to a conflict: 'Violation of PRIMARY KEY constraint 'PK_dbo.Hotels'. Cannot insert duplicate key in object 'dbo.Hotels'. The duplicate key value is (0e6e1bae-bd59-46ac-9630-a2b53dd04a90).\r\nThe statement has been terminated.
But why on earth is it attemping to INSERT my child object again? Firstly, I haven't altered it, and secondly, it has an Id, CreatedAt etc.
I cannot find any similar issues regarding Azure Mobile Apps, but I did find this SO Post regarding Entity Framework but the OP talks about having manually created the children, so I am not sure it fully applies as I have fetched the child Entity from the DB through the TableController.
Azure Mobile Apps does not support relationships. You are bumping into one of the many issues that go along with that.
If you are using offline sync, then decompose the tables so that the linkage is less required, then sync each table individually.
If you are not using offline sync, use a custom API to commit the change to the database.

Asp.NET HttpClient with custom JsonConverter

Hi I have the following code to get data from a REST service:
HttpResponseMessage response;
response = client.GetAsync("CatalogUpdate/" + sessionId).Result;
if (response.IsSuccessStatusCode)
{
catalogs = response.Content.ReadAsAsync<List<Models.CatalogInfo>>().Result;
}
My CatalogInfo class is:
public class CatalogInfo
{
public CatalogInfo(int id,string name,string date)
{
this.ID = id;
this.Name = name;
this.Date = date;
}
public int ID { get; set; }
public string Name { get; set; }
public string Date { get; set; }
}
And the jSON that Im getting from the REST service is:
{"error":false,"locations":[{"ID":"3","ABC":"XC","Description":"Rome","Status":"1"},{"ID":"4","CD1":"XH","Description":"Italy","Status":"1"}]}
I want to map the jSON to my CatalogInfo class, is there a way to do this?
The easiest option here is to use Json.NET and to create classes that represent the expected JSON, so for example:
class Location
{
public string ID { get; set; }
public string Description { get; set; }
}
class JSONResponse
{
[JsonProperty("error")]
public bool Error { get; set; }
[JsonProperty("locations")]
public Location[] Locations { get; set; }
}
We don't have to implement every property as Json.NET will just ignore what isn't there.
Then deserialize the response. In your case you're using HttpResonseMessage so something like this:
JSONResponse response = JsonConvert.DeserializeObject<JSONResponse>(
await response.Content.ReadAsStringAsync()
);
You can then use LINQ to convert the locations over to your object:
CatalogInfo[] catalog = response.Locations.Select(loc => new CatalogInfo(
loc.ID,
loc.Description,
String.Empty
)).ToArray();

Categories

Resources