I am practicing with web api. My goal is to create a Get endpoint, which receive data from an external api, then return a different result. external api link: https://www.themealdb.com/api/json/v1/1/search.php?f=a, The external api data looks like:
{
"meals": [
{
"idMeal": "52768",
"strMeal": "Apple Frangipan Tart",
"strDrinkAlternate": null,
"strCategory": "Dessert",
.....
},
{
"idMeal": "52893",
"strMeal": "Apple & Blackberry Crumble",
....
}
]
}
I want my endpoint provide a different result like the following:
[
{
"idMeal": "52768",
"strMeal": "Apple Frangipan Tart",
"ingredients": ["Apple", "sugar"...]
},
{
"idMeal": "52893",
"strMeal": "Apple & Blackberry Crumble",
"ingredients": ["Apple", "sugar"...]
}
]
The following code is what I attempted so far, It's working, but the moment I changed property ingredient1 from public to private, that ingredient in list will become null, also, there are so many ingredients, some of them are null by default, I don't want to add them if they are null, how can I fix these two issues? Thanks a lot
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using RestSharp;
namespace testAPI.Controllers;
public class Content
{
[JsonPropertyName("meals")]
public List<Meal> Meals { get; set; }
}
public class Meal
{
[JsonPropertyName("idMeal")]
public string MealId { get; set; }
[JsonPropertyName("strMeal")]
public string Name { get; set; }
[JsonPropertyName("strIngredient1")]
public string Ingredient1 { get; set; }
[JsonPropertyName("strIngredient2")]
public string Ingredient2 { get; set; }
[JsonPropertyName("strIngredient20")]
public string Ingredient20 { get; set; }
public List<string> Ingredients
{
get { return new List<string>(){Ingredient1, Ingredient2, Ingredient20};}
}
}
[ApiController]
[Route("api/[controller]")]
public class DishesController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAllRecipes()
{
var client = new RestClient($"https://www.themealdb.com/api/json/v1/1/search.php?s=");
var request = new RestRequest();
var response = await client.ExecuteAsync(request);
var mealList = JsonSerializer.Deserialize<Content>(response.Content);
return Ok(mealList.Meals);
}
}
To address the problems one at a time...
the moment I changed property ingredient1 from public to private, that ingredient in list will become null
Changing the access modifier affects both deserialization and serialization, so this cannot be used to only stop it from serializing the property. You should split the data models up into what you want to receive and what you want to expose/return.
there are so many ingredients, some of them are null by default, I don't want to add them if they are null
Addition to splitting up the data models you can handle this when mapping from one model to the other.
The following code should fix both issues:
namespace TheMealDb.Models
{
// These are the models you receive from TheMealDb
// JSON converted to classes with https://json2csharp.com/
public class Root
{
public List<Meal> meals { get; set; }
}
public class Meal
{
public string idMeal { get; set; }
public string strMeal { get; set; }
public string strIngredient1 { get; set; }
public string strIngredient2 { get; set; }
public string strIngredient3 { get; set; }
// Other properties removed for brevity...
}
}
namespace Internal.Models
{
// This is the model you want to return from your controller action
public class Meal
{
[JsonPropertyName("id")] // No need to use the same name as from themealdb
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("ingredients")]
public List<string> Ingredients { get; set; }
}
}
Now, to fetch, map and return the data in your controller action:
[HttpGet]
public async Task<IActionResult> GetAllRecipes()
{
var client = new RestClient($"https://www.themealdb.com/api/json/v1/1/search.php?s=");
var request = new RestRequest();
var response = await client.ExecuteAsync(request);
// Deserialize to the "TheMealDb" models
var mealList = JsonSerializer.Deserialize<TheMealDb.Models.Root>(response.Content);
// Map to your own models
var myMealList = mealDbList.meals?.Select(MapToInternal);
return Ok(myMealList);
}
// Map "TheMealDb" model to your own model
private Internal.Models.Meal MapToInternal(TheMealDb.Models.Meal externalMeal)
{
return new Internal.Models.Meal
{
Id = externalMeal.idMeal,
Name = externalMeal.strMeal,
Ingredients = new []
{
externalMeal.strIngredient1,
externalMeal.strIngredient2,
externalMeal.strIngredient3,
// ...
}
// Remove empty/null ingredients
.Where(ingr => !string.IsNullOrEmpty(ingr))
.ToList()
};
}
See the code in action.
Related
Question
When using an anonymous type it works, but not when using the type ClientGroupView, which is identical to the anonymous type. Why?
I suspect this has to do with the [JsonIgnore] attribute - it somehow persists with the typed object, but not with the anonymous object.
Background
I've got a many-to-many relationship between Clients and ClientGroups and a controller that returns the groups for a given client.
The controller method returns Json with an empty dictionary, when I use a typed object, but not if I use an anonymous object, then it returns the expected Json. I get the same results with both .NET 5 and .Net 6.
When using an anonymous object, the Json returned is:
[{
"group":{ "clientGroupId":1, "groupName":"ClientGroup1" },
"permissions":{ "rClientGroupClientId":1, "clientGroupId":1, "clientId":1 }
}]
When using an object of type ClientGroupView (that does not contain any [JsonIgnore] attrubutes), the Json returned is:
[{}]
I both cases result is the same, before converting to Json, with return Ok(result).
The controller
The controller method that returns the information for a given ClientId:
[HttpGet]
[Route("{id}")]
public async Task<IActionResult> GetClientGroups(int id)
{
var client = await db.Clients.FindAsync(id);
if (client == null)
return NotFound();
// Works when using an anonymous type - the Json dictionary is not empty.
var groups = db.Entry(client)
.Collection(r => r.XClientGroups)
.Query()
.Include(r => r.ClientGroup)
.Select(r => new { r.ClientGroup, r });
// But fails when not using an anonymous type - the json dictionary is empty.
//var groups = db.Entry(client)
// .Collection(r => r.XClientGroups)
// .Query()
// .Include(r => r.ClientGroup)
// .Select(r => new ClientGroupView(r.ClientGroup, r));
var result = await groups.ToListAsync();
if (result == null || result.Count <= 0)
return NotFound();
return Ok(result);
}
The Client
public class Client
{
public int ClientId { get; set; }
public string ClientName { get; set; } = string.Empty;
[JsonIgnore]
public virtual ICollection<X_ClientGroup_Client> XClientGroups { get; set; }
public Client()
{
XClientGroups = new HashSet<X_ClientGroup_Client>();
}
}
The ClientGroup
public class ClientGroup
{
public int ClientGroupId { get; set; }
public string GroupName { get; set; }
[JsonIgnore]
public virtual ICollection<X_ClientGroup_Client> XClients { get; set; }
public ClientGroup()
{
XClients = new HashSet<X_ClientGroup_Client>();
}
}
The X_ClientGroup_Client
The many-to-many relation between Client and ClientGroup:
public class X_ClientGroup_Client
{
public int RClientGroupClientId { get; set; }
public int ClientGroupId { get; set; }
public int ClientId { get; set; }
[JsonIgnore]
public ClientGroup ClientGroup { get; set; }
[JsonIgnore]
public Client Client { get; set; }
}
The ClientGroupView
Here is the ClientGroupView that corresponds to the anonymous object. It doesn't contain any [JsonIgnore] attributes:
public class ClientGroupView
{
public ClientGroup Group;
public X_ClientGroup_Client Permissions;
public ClientGroupView(ClientGroup group, X_ClientGroup_Client permissions)
{
this.Group = group;
this.Permissions = permissions;
}
}
If I remove the constructor and construct the object like I do with the anonymous object, the result is the same.
A solution is to change ClientGroupView to contain the fields instead of the classes, but I prefer that it contained the classes instead of the fields of the classes, because it is easier to maintain:
// I do not want to use this version of ClientGroupView
public class ClientGroupView
{
public int RClientGroupClientId { get; set; }
public int ClientId { get; set; }
public int ClientGroupId { get; set; }
public string GroupName { get; set; }
public ClientGroupView(ClientGroup clientgroup, X_ClientGroup_Client permissions)
{
RClientGroupClientId = permissions.RClientGroupClientId;
ClientId = permissions.ClientId;
ClientGroupId = clientgroup.ClientGroupId;
GroupName = clientgroup.GroupName;
}
}
The reason I don't want to use this version, is because it requires more maintenance and because the controller is used internally, i.e. I do not need to use a DTO.
ClientGroupView.Group and ClientGroupView.Permissions are fields, not properties.
Add { get; set; }.
I'm developing an api in net core.
I've done a post function in which I send an object containing multiple parameters and a list within another list.
When I'm debugging the code the function is called correctly but I find that the second list always arrives null.
The rest of the data arrives at you correctly. I have done different tests with other objects and everything works correctly.
It is this case in which the list within another the second one arrives null.
My code:
example request input
{
"Name": "TestName",
"Related1":
[{
"id1": "TestNameRelated1",
"Related2":
[{
"id2": "TestNameRelated2"
}]
}]
}
[HttpPost]
public resultExample Test([FromBody]TestClass test)
{
//do something
}
[DataContract]
public class TestClass
{
[DataMember]
public string Name { get; set; }
[DataMember]
public List<TestClassArray> Related1 { get; set; }
}
[DataContract]
public class TestClassArray
{
[DataMember]
public string id1 { get; set; }
[DataMember]
public List<TestClassArray2> Related2 { get; set; }
}
[DataContract]
public class TestClassArray2
{
[DataMember]
public string id2 { get; set; }
}
This api was previously made in .NET framework 4.8 and this case worked correctly.
Now I'm passing the api to .Net5.
Could it be that in .Net5 it is not allowed to pass lists within other lists?
Do you have to enable some kind of configuration to be able to do this now?
You need use class/DTO with constructor like shown below and you should be good to go. I have uploaded this sample API app's code working with .net5.0 on my GitHub here.
public class TestClass
{
public TestClass()
{
Related1 = new List<TestClassArray>();
}
public string Name { get; set; }
public List<TestClassArray> Related1 { get; set; }
}
public class TestClassArray
{
public TestClassArray()
{
Related2 = new List<TestClassArray2>();
}
public string id1 { get; set; }
public List<TestClassArray2> Related2 { get; set; }
}
public class TestClassArray2
{
public string id2 { get; set; }
}
public class ResultExample
{
public string StatusCode { get; set; }
public string Message { get; set; }
}
Controller Post Method
[HttpPost]
[ProducesResponseType(typeof(ResultExample), 200)]
public ResultExample Post([FromBody] TestClass test)
{
ResultExample testResult = new ResultExample();
TestClass test2 = new TestClass();
TestClassArray testClassArray = new TestClassArray();
TestClassArray2 testClassArray2 = new TestClassArray2();
test2.Name = test.Name;
foreach (var item in test.Related1)
{
foreach (var item2 in item.Related2)
{
testClassArray2.id2 = item2.id2;
}
testClassArray.Related2.Add(testClassArray2);
}
test2.Related1.Add(testClassArray);
Console.WriteLine(test2);
testResult.Message = "New Result added successfullly....";
testResult.StatusCode = "201";
return testResult;
}
Swagger Input Sample Payload
Post Controller Result
Response of Sample input payload,(You can change it to default 201 response code as well)
I had a similar issue.
API method shows List was null
In my case a date field was not well formatted
So I use SimpleDateFormat on Android Studio with a correct datetime format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",Locale.US);
item.setDate(dateFormat.format(calendar.getTime()));
and works fine
Currently I am doing an API call via jQuery, my question is, is there away to do this call in C# or away to convert the results of an API call to an ASP.NET List of Model Objects?
Here is my Model
public class TeamStatsClass
{
public int id { get; set; }
public string name { get; set; }
public string league { get; set; }
public string division { get; set; }
}
And here is my current ajax call
$.ajax({
url: "https://statsapi.web.nhl.com/api/v1/teams?sportId=1",
success: function (data) {
for (var team of data.teams) {
console.log(team.name);
}
}
});
UPDATE
I changed my classes to look like so:
public class StatsTeamsClass
{
public IEnumerable<Teams> teams { get; set; }
public string copyright { get; set; }
}
public class Division
{
public int id { get; set; }
public string name { get; set; }
public string link { get; set; }
}
public class Teams
{
public int id { get; set; }
public string name { get; set; }
public string link { get; set; }
public League league { get; set; }
public Division division { get; set; }
}
and created this method which indeeds puts the results in model object:
public async System.Threading.Tasks.Task<StatsTeamsClass> GetTeams()
{
HttpClient Http = new HttpClient();
var json = await Http.GetStringAsync("https://statsapi.web.nhl.com/api/v1/teams?sportId=1");
StatsTeamsClass teams = JsonConvert.DeserializeObject<StatsTeamsClass>(json);
return teams;
}
But when I try to call this method in another controller, it just hangs there, no error, no nothing, I am assuming it will just time out after a while
public class HomeController : Controller
{
APIController webService = new APIController();
public ActionResult Index()
{
var item = webService.GetTeams().Result.teams;
return View();
}
}
(GetTeams() is inside the controller APIController)
So what would be the proper way to A. get the results of an API in object model and then call those results?
The controller action needs to be made async as well to avoid mixing async-await and blocking calls like .Result or .Wait() that could potentially cause deadlocks.
Reference Async/Await - Best Practices in Asynchronous Programming
public class HomeController : Controller {
APIController webService = new APIController();
public async Task<ActionResult> Index() {
var model = await webService.GetTeams();
var teams = model.teams;
return View();
}
}
Assuming APIController is an actual ApiContoller
public class APIController : ApiController {
//Your original code
public async Task<StatsTeamsClass> GetTeams() {
HttpClient Http = new HttpClient();
var json = await Http.GetStringAsync("https://statsapi.web.nhl.com/api/v1/teams?sportId=1");
StatsTeamsClass teams = JsonConvert.DeserializeObject<StatsTeamsClass>(json);
return teams;
}
//...
}
I would suggest not calling APIController directly like that from the HomeController and instead extract the GetTeams() method out into a reusable service
public class WebService {
static Lazy<HttpClient> http = new Lazy<HttpClient>();
public async Task<T> GetAsync<T>(string url) {
var json = await http.Value.GetStringAsync(url);
return JsonConvert.DeserializeObject<T>(json);
}
public Task<StatsTeamsClass> GetTeamsAsync() {
var url = "https://statsapi.web.nhl.com/api/v1/teams?sportId=1";
return GetAsync<StatsTeamsClass>(url);
}
}
Reference You're using HttpClient wrong
that can be properly used in HomeController
public class HomeController : Controller {
public async Task<ActionResult> Index() {
// Ideally web service should be injected but that topic
// is outside of the scope of the question at the moment.
var webService = new WebService();
var model = await webService.GetTeamsAsync();
var teams = model.teams;
//...
return View(teams);
}
}
The assumption here is that the project is a mixed Asp.Net MVC and Web Api 2+
Index.cshtml
#model IEnumerable<Teams>
#{
ViewBag.Title = "Teams";
}
#if(Model != null && Model.Count() > 0) {
#foreach (var #team in Model) {
<p>#team.name</p>
}
}
Yes, the equivalent in C# would be to use HttpClient. You're best off creating a static instance of the class that you reuse for a particular kind of repeated call:
private static readonly HttpClient Http = new HttpClient();
and then used it from an async method using Newtonsoft.Json like this:
var json = await Http.GetStringAsync("https://statsapi.web.nhl.com/api/v1/teams?sportId=1");
You can then parse this string of JSON into a model class like this:
var model = JsonConvert.DeserializeObject<TeamStatsClass>(json);
As the question is answered by #Daniel above just want to add couple of more points here The json you are getting cannot be directly casted to TeamStatsClass you might have to introduce another base class as teams is the collection in the json you are getting.
Im posting it here to get a clearer view
public class ResponseBaseClass
{
public IEnumerable<TeamStatsClass> teams { get; set; }
public string copyright { get; set; }
}
public class TeamStatsClass
{
public int id { get; set; }
public string name { get; set; }
public Division division { get; set; }
}
public class Division
{
public int id { get; set; }
public string name { get; set; }
public string nameShort { get; set; }
public string link { get; set; }
}
HttpClient Http = new HttpClient();
var json = await Http.GetStringAsync("https://statsapi.web.nhl.com/api/v1/teams?sportId=1");
var model = JsonConvert.DeserializeObject<ResponseBaseClass>(json);
var yourTeamModelObj = model.teams;
I've got a list of objects in JSON that isn't recognized by a WebApi2 controller
The JSON list is the following:
{
"FirstObjectType": [{"Name": "the_name"}],
"SecondObjectType": [{"Label": "01_obj"}, {"Label": "02_obj"}]
}
The Model class is:
public class CompositeObject
{
[JsonProperty("FirstObjectType")]
public List<FirstObject> fo { get; set; }
[JsonProperty("SecondObjectType")]
public List<SecondObject> so { get; set; }
}
The controller is:
public IHttpActionResult PostList([FromBody] CompositeObject jsonList)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
List<FirstObject> fo_list = jsonList.fo;
foreach (var item in fo_list)
{
db.FirstObject.Add(item);
db.SaveChanges();
}
return StatusCode(HttpStatusCode.OK);
}
When I submit the Post action, the controller recognize both lists in CompositeObject jsonList as Null
There is a problem in your model, where names are not being matched. You have to update model as:
public class FirstObjectType
{
public string Name { get; set; }
}
public class SecondObjectType
{
public string Label { get; set; }
}
public class RootObject
{
public List<FirstObjectType> FirstObjectType { get; set; }
public List<SecondObjectType> SecondObjectType { get; set; }
}
I have assumed that FirstObjectType contains string with name Name and SecondObjectType contains string with name Label. Make sure to use same names for properties of FirstObjectType and SecondObjectType class as in JSON string.
The issue was in the client code because I missed to set the Content-type as application/json in the header section.
In this way the WebApi server doesn't recognize in the right way the JSON object (I think that the server look for a x-www-form-urlencoded type)
So, the code above is right, but I have found another solution
In the WebApi controller:
using Newtonsoft.Json.Linq;
public IHttpActionResult PostList([FromBody] JObject ReceivedObjectsList)
{
var receivedLists = ReceivedObjectsList.Properties();
List<FirstObject> fo = ReceivedObjectsList["FirstObjectType"].ToObject<List<FirstObject>>();
List<SecondObject> so = ReceivedObjectsList["SecondObjectType"].ToObject<List<SecondObject>>();
...
}
I have a php webservice which gets the data from database and returns it as json data.
Json data
{"faqs":
[
{"faq":{"id":"123"}},
{"faq":{"id":"124"}}
]
}
Object classes
public class FaqList
{
public List<Faq> faqs { get; set; }
}
public class Faq
{
public string id { get; set; }
}
Test class
var client = new HttpClient();
HttpResponseMessage response = await client.GetAsync(new Uri("http://www.mydomain.com/webservice/7/server.php"));
var jsonString = await response.Content.ReadAsStringAsync();
FaqList list = JsonConvert.DeserializeObject<FaqList>(jsonString);
list.faqs.Count() => 2
list.faqs[0].id => NULL !!
I fill all the objects to the 'list'. With count test I see that it's filled. But if I try to get an objects from it, I get null error.
So, how can I correctly fill my list so that I can get the objects from it?
http://json2csharp.com/ suggests these definitions. (One more class Faq2)
public class Faq2
{
public string id { get; set; }
}
public class Faq
{
public Faq2 faq { get; set; }
}
public class RootObject
{
public List<Faq> faqs { get; set; }
}