I have an API method that looks like this:
[HttpGet("top/{max}", Name = "GetTopLocations")]
public ActionResult<List<LocationDto>> GetTop(int max)
{
return _locationService.Get();
}
I have then generated client code class with NSwag via Swagger. I call this method in my Blazor WebAssembly code like this:
Locations = await MyProject.Client.GetTopLocationsAsync(10);
However, this generates an exception:
Error: 0 : Could not deserialize the response body stream as System.Collections.Generic.ICollection`1[[MyProject.LocationDto, MyProject.Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].
But if I call this method in my browser like this: http://localhost:50464/api/Locations/top/10
and then take that JSON string and test to Deserialize it like this:
string jsonStringCopiedFromBrowser = "...";
var thisWorks = JsonConvert.DeserializeObject<ICollection<LocationDto>>(jsonStringCopiedFromBrowser);
I cannot understand why it works when I deserialize it like in the last code but not from NSwag? Any tips on what I am doing wrong? I have not modified the NSwag generated code at all, its taken directly from the generated output.
NSwag Studio does not help with this kind of operation.
so you need to create a List mannunally.
I try and its work
here is the code below
public class Response<T>
{
public string Message { get; set; }
public string ValidatonError { get; set; }
public bool Success { get; set; }
public T Data { get; set; }
public IList<T> DataEnum { get; set; }
}
public async Task<Response<EmployeeDetailDTO>> GetEmployee(int ID)
{
Response<EmployeeDetailDTO> response;
try
{
var data = await client.EmployeeAllAsync(ID);
response = new Response<EmployeeDetailDTO>
{
DataEnum = data.ToList(),
Success = true
};
}
catch (ApiException e)
{
response = ConvertApiException<EmployeeDTO>(e);
}
return response;
}
Now extract the result on your Client Page
#foreach(var model in rl.ToArray())
{
<td>#model.Complaintname</td>
}
after a couple of hours, I use this method for solving the problem. NSwag Studio work if you send a single Record.
Related
I am attempting to use the AmazonSimpleEmailService client via the AWS-SDK for .Net, to send a SendBulkTempatedEmailRequest. I have implemented a dedicated handler for actually building the request and making the SendBulkTemplatedEmailAsync call. It is not working as I expect. I think there is a bug with how the request object is serialized and passed to the API.
Here is some sample code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.SimpleEmail;
using Amazon.SimpleEmail.Model;
using Newtonsoft.Json;
namespace Sample.AWS.SES
{
public class SendEmailService
{
private readonly IAmazonSimpleEmailService _sesClient;
public SendEmailService(IAmazonSimpleEmailService sesClient)
{
_sesClient = sesClient;
}
public async Task<string> SendBulkEmailAsync(SesOrderCreatedBulkTemplate data)
{
var result = string.Empty;
var request = new SendBulkTemplatedEmailRequest
{
Template = data.Template,
ConfigurationSetName = data.ConfigurationSet,
DefaultTemplateData = JsonConvert.SerializeObject(data.DefaultTemplateData),
Source = data.Source,
Destinations = data.Destinations
.Select(d => new BulkEmailDestination
{
Destination = new Destination
{
ToAddresses = d.ToAddresses.ToList(),
},
ReplacementTemplateData = string.Empty
})
.ToList(),
ReplyToAddresses = data.ReplyToAddresses.ToList()
};
try
{
var resp = await _sesClient.SendBulkTemplatedEmailAsync(request);
}
catch (Exception ex)
{
var msgEx = new Exception("Error sending message to SES.", ex);
throw msgEx;
}
return result;
}
public class SesOrderCreatedBulkTemplate
{
public string Source { get; set; }
public string Template { get; set; }
public string ConfigurationSet { get; set; }
public IEnumerable<Destination> Destinations { get; set; }
public MyTemplateData DefaultTemplateData { get; set; }
public IEnumerable<string> ReplyToAddresses { get; set; }
public string ReturnPath { get; set; } = string.Empty;
}
public class DestinationObj
{
public IEnumerable<string> ToAddresses { get; set; }
public MyTemplateData ReplacementTemplateData { get; set; }
public DestinationObj() {}
}
public class MyTemplateData
{
public List<Person> Tenants { get; set; }
}
public class Person
{
public string PersonName { get; set; }
public List<object> PersonData { get; set; }
}
}
}
The properties for SourceArn, TemplateArn and ReturnPathArn are omitted on purpose. According the SES documentation, the SDK wraps the low-level functionality of the Amazon SES API with higher-level data types and function calls that take care of the details for you. When I view the API documentation for sending bulk email, the ARN properties are all list as not required. When I look at the some CLI examples, it is the same. When I look at the documentation for the SDK for .Net v3, it is ambiguous (not marked as required or optional).
Because the SDK supposed to wrap the low-level functionality of the API, I do not believe the ARN values are required (neither the API nor the CLI require them). However, when I attempt to actually use the request object created in the code snippet, I get an error that says InvalidTemplateData.
If I serialize the request object to JSON, then remove the 3 ARN fields from the string, I can use either the API or the CLI to successfully send the message.
In addition to not specifying a value for the ARN's, I have tried (for all 3 ARN values):
specificArn = string.empty;
specificArn = new {};
specificArn = "";
I have also tried explicitly newing-up the object separate from initializing the properties:
var request = new SendBulkTemplatedEmailRequest();, and then individually populating the properties.
If I don't initialize the ARN values, I get an error about NoneType vs StringType when the send method is called. The variations on string initialization that I tried result in InvalidTemplateData errors.
Note, I do know ARN values for Source and ReturnPath. I do not have an ARN value for the template we use. Supposedly, using the CLI, when you create a template you should receive a response back that includes the ARN for the template. I get no response from the CLI when I create a template, but it does get created every time I try. The describe-template CLI command is not valid when you specify SES and responds with an error if I don't specify the workspace (whatever you call the SES space) value.
Does anyone have a suggestion on how to solve this?
From the provided code it's hard to say what you pass into API.
This is how I send bulk emails:
SES configuration
create a template (taken from AWS SES docs) and save it to a file - my-template.json
{
"Template": {
"TemplateName": "my-template",
"SubjectPart": "Greetings, {{name}}!",
"HtmlPart": "<h1>Hello {{name}},</h1><p>Your favorite animal is {{favoriteanimal}}.</p>",
"TextPart": "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}."
}
}
create a template via CLI aws ses create-template --cli-input-json file://my-template.json
SES .NET SDK API
async Task SendAsync(string[] receivers)
{
var destinations = receivers
.Select(receiver => new BulkEmailDestination
{
Destination = new Destination(new List<string> { receiver }),
ReplacementTemplateData = ToJson(receiver, "Doggy")
})
.ToList();
var bulkTemplate = new SendBulkTemplatedEmailRequest
{
Source = "your-email#gmail.com", // your email you bulk send from
Template = "my-template", // your template name
DefaultTemplateData = ToJson("<not set>", "<not set>"),
Destinations = destinations
};
await _client.SendBulkTemplatedEmailAsync(bulkTemplate);
}
// Create replacement data by serializing Dictionary
string ToJson(string name, string favoriteanimal)
=> JsonSerializer.Serialize(new Dictionary<string, string>
{
{ "name", name },
{ "favoriteanimal", favoriteanimal }
});
I've been fighting with this for a while and I think is time to ask for some help.
I'm using RestSharp to connect to my local PHP REST API. The format I'm returning the api results is as follows (JSON):
{
"estado": "login_correcto",
"content": {
"id": 1,
"nombreusuario": "daniaguado",
"contrasena": "qwerty",
"email": "daniel#zadecon.com",
"nivelacceso": 5,
"created_at": "2017-08-01 10:31:16",
"updated_at": "-0001-11-30 00:00:00"
}
}
For this, I've created a custom Usuario class (User) and a custom ResponseUsuario as follows:
class Usuario
{
public int id { get; set; }
public string nombreusuario { get; set; }
public string contrasena { get; set; }
public string email { get; set; }
public int nivelacceso { get; set; }
public string created_at { get; set; }
public string updated_at { get; set; }
}
Then the ResponseUsuario:
class ResponseUsuario
{
public string estado { get; set; }
public Usuario content { get; set; }
}
The response is being parsed ok when the response is ok (202) and the user exists.
But, when the login is incorrect, in "content" I'm returning a message, not an User:
{
"estado": "login_incorrecto",
"content": "La contraseña es incorrecta / Password incorrect"
}
So, If I use ResponseUsuario there, queryResult.Data is null, because it cannot map the content to the Usuario class. The fact is that if I don't use ResponseUsuario and instead use a Response class, in which content variable is type object, I cannot cast it to Usuario and I cannot deal with it.
This is the more general Response class which I understand I should be using for all my queries:
class Response
{
public string estado { get; set; }
public object content { get; set; }
}
Finally, this is my RestSharp query:
ApiClient api = new ApiClient(); // Just create the client with some common parameters
var request = new RestRequest("usuarios/login", RestSharp.Method.POST);
request.AddParameter("nombreusuario", textbox_usuario.Text);
request.AddParameter("contrasena", textbox_contrasena.Text);
var queryResult = api.cliente.Execute<ResponseUsuario>(request);
if (queryResult.StatusCode == System.Net.HttpStatusCode.OK)
{
Usuario u = (Usuario) queryResult.Data.content; // Gives error when using Execute<Response> (object content)
String s = queryResult.Data.estado;
Console.Out.WriteLineAsync($"OK - {u.nombreusuario} - {u.email} - Acceso: {u.nivelacceso}");
}
else
{
Console.Out.WriteLineAsync($"Query Failed: {queryResult.StatusDescription}, estado: {queryResult.Data.estado}"); // Giving error when using Execute<ResponseUsuario>
}
How can I fix the cast to Usuario (or the matching class for content)? I think the Response class should be common to all my queries to the API and then cast the Content part to it's appropiate class, but I don't know how to do it.
Really late to the party. I have met a similar problem when an API returned different structures on error vs. 200. I gave up RestSharp's typed call and explicitly deserialize the payload.
var response = RestClient.Execute<IdamValidationResultApiModel>(request);
if (response.IsSuccessful)
var t1 = JsonConvert.DeserializeObject<FirstType>(response.Content);
else
var t2 = JsonConvert.DeserializeObject<SecondType>(response.Content);
check if (queryResult.Data.content is Usuario) and cast only if it is true
else cast to string.
Does each response return a different Http status code? I'm guessing that you're returning a 401 from your rest api. If this is the case:
You can then use a switch to deal with other status codes.
HttpStatusCode statusCode = queryResult.StatusCode;
int numericStatusCode = (int)statusCode;
switch (numericStatusCode)
{
case 202:
dynamic content = queryResult.Data.content;
Usuario u = content.content;
String s = queryResult.Data.estado;
Console.Out.WriteLineAsync($"OK - {u.nombreusuario} - {u.email} - Acceso: {u.nivelacceso}");
break;
case 401:
//Deal with the failed login
break;
}
Finally, the reason that you're getting an error, is because you're trying to cast the response object which is of type UsarioResponse to Usario. You need to navigate the json to find content.content (as seen above)
I finally solved the issue by generating a custom Reflection from the Dictionary Object to the class, that is, at the end, a custom parser.
I created a new class Tools and added a conversion function:
public static T CastToObject<T>(IDictionary<string, object> dict) where T : class
{
Type type = typeof(T);
T result = (T)Activator.CreateInstance(type);
foreach (var item in dict)
{
type.GetProperty(item.Key).SetValue(result, item.Value, null);
}
return result;
}
And on the Api Query, added a switch-case depending on the Status code:
var queryResult = api.cliente.Execute<Response>(request);
switch(queryResult.StatusCode)
{
case System.Net.HttpStatusCode.OK:
Usuario u = Tools.CastToObject<Usuario>((IDictionary<string, object>)queryResult.Data.content);
String status = queryResult.Data.estado;
Console.Out.WriteLineAsync($"OK - {u.nombreusuario} - {u.email} - Acceso: {u.nivelacceso}");
break;
case System.Net.HttpStatusCode.NotFound:
Console.Out.WriteLineAsync($"Login Failed: User does not exists");
break;
case System.Net.HttpStatusCode.Unauthorized:
Console.Out.WriteLineAsync($"Login Failed: Incorrect Password");
break;
default:
Console.Out.WriteLineAsync($"Error when connecting to the API");
break;
}
Further I'll need to deal with responses containing list inside the content field but I think the procedure will be something similar to this one: to create a custom parser for the content field and cast it to my class.
My 1st question, so please be kind... :)
I'm using the C# HttpClient to invoke Jobs API Endpoint.
Here's the endpoint: Jobs API Endpoint (doesn't require key, you can click it)
This gives me JSON like so.
{
"count": 1117,
"firstDocument": 1,
"lastDocument": 50,
"nextUrl": "\/api\/rest\/jobsearch\/v1\/simple.json?areacode=&country=&state=&skill=ruby&city=&text=&ip=&diceid=&page=2",
"resultItemList": [
{
"detailUrl": "http:\/\/www.dice.com\/job\/result\/90887031\/918715?src=19",
"jobTitle": "Sr Security Engineer",
"company": "Accelon Inc",
"location": "San Francisco, CA",
"date": "2017-03-30"
},
{
"detailUrl": "http:\/\/www.dice.com\/job\/result\/cybercod\/BB7-13647094?src=19",
"jobTitle": "Platform Engineer - Ruby on Rails, AWS",
"company": "CyberCoders",
"location": "New York, NY",
"date": "2017-04-16"
}
]
}
I've pasted a complete JSON snippet so you can use it in your answer. The full results are really long for here.
Here's are the C# classes.
using Newtonsoft.Json;
using System.Collections.Generic;
namespace MyNameSpace
{
public class DiceApiJobWrapper
{
public int count { get; set; }
public int firstDocument { get; set; }
public int lastDocument { get; set; }
public string nextUrl { get; set; }
[JsonProperty("resultItemList")]
public List<DiceApiJob> DiceApiJobs { get; set; }
}
public class DiceApiJob
{
public string detailUrl { get; set; }
public string jobTitle { get; set; }
public string company { get; set; }
public string location { get; set; }
public string date { get; set; }
}
}
When I invoke the URL using HttpClient and deserialize using JSON.NET, I do get the data back properly.
Here's the code I am calling from my Console App's Main method (hence the static list, I think this could be better refactored??)
private static List<DiceApiJob> GetDiceJobs()
{
HttpClient httpClient = new HttpClient();
var jobs = new List<DiceApiJob>();
var task = httpClient.GetAsync("http://service.dice.com/api/rest/jobsearch/v1/simple.json?skill=ruby")
.ContinueWith((taskwithresponse) =>
{
var response = taskwithresponse.Result;
var jsonString = response.Content.ReadAsStringAsync();
jsonString.Wait();
var result = JsonConvert.DeserializeObject<DiceApiJobWrapper>(jsonString.Result);
if (result != null)
{
if (result.DiceApiJobs.Any())
jobs = result.DiceApiJobs.ToList();
if (result.nextUrl != null)
{
//
// do this GetDiceJobs again in a loop? How?? Any other efficient elegant way??
}
}
});
task.Wait();
return jobs;
}
But now, how do I check if there are more jobs using the nextUrl field? I know I can check to see if it's not null, and if if not, that means there are more jobs to pull down.
Results from my debugging and stepping through
How do I do this recursively, and without hanging and with some delays so I don't cross the API limits? I think I have to use TPL ( Task Parallel Library) but am quite baffled.
Thank you!
~Sean
If you are concerned about response time of your app and would like to return some results before you actually get all pages/data from the API, you could run your process in a loop and also give it a callback method to execute as it gets each page of data from the API.
Here is a sample:
public class Program
{
public static void Main(string[] args)
{
var jobs = GetDiceJobsAsync(Program.ResultCallBack).Result;
Console.WriteLine($"\nAll {jobs.Count} jobs displayed");
Console.ReadLine();
}
private static async Task<List<DiceApiJob>> GetDiceJobsAsync(Action<DiceApiJobWrapper> callBack = null)
{
var jobs = new List<DiceApiJob>();
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("http://service.dice.com");
var nextUrl = "/api/rest/jobsearch/v1/simple.json?skill=ruby";
do
{
await httpClient.GetAsync(nextUrl)
.ContinueWith(async (jobSearchTask) =>
{
var response = await jobSearchTask;
if (response.IsSuccessStatusCode)
{
string jsonString = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<DiceApiJobWrapper>(jsonString);
if (result != null)
{
// Build the full list to return later after the loop.
if (result.DiceApiJobs.Any())
jobs.AddRange(result.DiceApiJobs.ToList());
// Run the callback method, passing the current page of data from the API.
if (callBack != null)
callBack(result);
// Get the URL for the next page
nextUrl = (result.nextUrl != null) ? result.nextUrl : string.Empty;
}
}
else
{
// End loop if we get an error response.
nextUrl = string.Empty;
}
});
} while (!string.IsNullOrEmpty(nextUrl));
return jobs;
}
private static void ResultCallBack(DiceApiJobWrapper jobSearchResult)
{
if (jobSearchResult != null && jobSearchResult.count > 0)
{
Console.WriteLine($"\nDisplaying jobs {jobSearchResult.firstDocument} to {jobSearchResult.lastDocument}");
foreach (var job in jobSearchResult.DiceApiJobs)
{
Console.WriteLine(job.jobTitle);
Console.WriteLine(job.company);
}
}
}
}
Note that the above sample allows the callback method to access each page of data as it is received by the GetDiceJobsAsync method. In this case, the console, displays each page as it becomes available. If you do not want the callback option, you can simply pass nothing to GetDiceJobsAsync.
But the GetDiceJobsAsync also returns all the jobs when it completes. So you can choose to act on the whole list at the end of GetDiceJobsAsync.
As for reaching API limits, you can insert a small delay within the loop, right before you repeat the loop. But when I tried it, I did not encounter the API limiting my requests so I did not include it in the sample.
We have got a Odata response as below:
"{\r\n \"#odata.context\":\"http://localhost/ApplicationService/model/$metadata#Edm.String\",\"value\":\"{\\\"Messages\\\":[\\\"message 1\\\",\\\"message 2\\\",\\\"message 3\\\",\\\"message 4\\\"],\\\"IsValidEntity\\\":false}\"\r\n}"
Now say we have a class:
public class myValidationResult
{
public myValidationResult()
{
Messages = new List<string>();
}
public List<string> Messages { get; set; }
public bool IsValidEntity { get; set; }
}
This class used in MyOdataController class as below:
public class MyODataController : ODataController
{
[Authorize(Roles = "Admin")]
public async Task<IHttpActionResult> Post(T entity)
{
myValidationResult vResult = new myValidationResult();
vResult.Messages.Add("message 1");
vResult.Messages.Add("message 2");
vResult.Messages.Add("message 3");
vResult.Messages.Add("message 4");
vResult.IsValidEntity = false;
var strResult = JsonConvert.SerializeObject(vResult);
var resp = Content(HttpStatusCode.BadRequest, strResult );
return resp;
}
}
For the client Consuming this, we created below Class:
public class OData<T>
{
[JsonProperty("odata.context")]
public string Metadata { get; set; }
public T value { get; set; }
}
In the method where we call the Odata method & store response in 'msg':
var resp = msg.Result.Content.ReadAsStringAsync().Result;
resp is:
"{\r\n \"#odata.context\":\"http://localhost/ApplicationService/model/$metadata#Edm.String\",\"value\":\"{\\\"Messages\\\":[\\\"message 1\\\",\\\"message 2\\\",\\\"message 3\\\",\\\"message 4\\\"],\\\"IsValidEntity\\\":false}\"\r\n}"
var odatares = JsonConvert.DeserializeObject<OData<myValidationResult>>(resp);
But the above line giving error:
Can not convert value\":\"{\\\"Messages\\\":[\\\"message 1\\\",\\\"message 2\\\",\\\"message 3\\\",\\\"message 4\\\"],\\\"IsValidEntity\\\":false} to <.....namespace......>myValidationResult
Please suggest accordingly.
The OData response contains a string, not an instance of myValidationResult. Also, the response looks like it's missing some backslashes. (Are you sure the response shown is exactly what you received from the service?)
You can either fix the serialization of myValidationResult on the service:
// Don't serialize vResult yourself. OData will do it for you.
var resp = Content(HttpStatusCode.BadRequest, vResult );
Or deserialize in two steps as follows.
var data = "{\r\n \"#odata.context\":\"http://localhost/ApplicationService/model/$metadata#Edm.String\",\"value\":\"{\\\"Messages\\\":[\\\"message 1\\\",\\\"message 2\\\",\\\"message 3\\\",\\\"message 4\\\"],\\\"IsValidEntity\\\":false}\"\r\n}";
var outer = Newtonsoft.Json.JsonConvert.DeserializeObject<OData<string>>(data);
var inner = Newtonsoft.Json.JsonConvert.DeserializeObject<myValidationResult>(outer.value);
One more thing: The JsonProperty on OData<T> should be named #odata.context.
In my case the OData response did not contain a string but an object array which contains the data string as its first element. So in this case reading the data should look like this:
var outer = Newtonsoft.Json.JsonConvert.DeserializeObject<OData<object[]>>(data);
var inner = Newtonsoft.Json.JsonConvert.DeserializeObject<myValidationResult>(outer.value[0].ToString());
I am trying to create a new QulaificationType for which the workers have to answer a question to gain the qualification.Below is my C# code. I am getting an error while using createQualificationType method in C# api. Please help.
using System;
using System.Collections.Generic;
using System.Text;
using Amazon.WebServices.MechanicalTurk;
using Amazon.WebServices.MechanicalTurk.Domain;
namespace CreateHITExample
{
class Program
{
static SimpleClient client = new SimpleClient();
static void Main(string[] args)
{
CreateNewHIT();
}
static void CreateNewHIT()
{
**QuestionFormQuestion** question = new QuestionFormQuestion();
question.IsRequired = true;
question.QuestionIdentifier = "1";
**ContentType qnContent = new ContentType();**
QualificationType qualType = client.CreateQualificationType("MyQual2", string.Empty, "My Qualification Type", QualificationTypeStatus.Active, 0, **question**, "680", 600, true, 100);
string qualTypeId = qualType.QualificationTypeId;
Console.WriteLine("Created Qualification Type ID 2: {0}", qualTypeId);
}
}
}
I have to pass the question object as the parameter to CreateQualificationType method.
As you can see from the above piece of code, question object is of class QuestionFormQuestion.
Below are the class definitions that might be of some help.
QuestionFormQuestion Class definition from AWS MTurk dotnet API:
public class QuestionFormQuestion
{
public QuestionFormQuestion();
public AnswerSpecificationType AnswerSpecification { get; set; }
public string DisplayName { get; set; }
public bool IsRequired { get; set; }
[XmlIgnore]
public bool IsRequiredSpecified { get; set; }
**public ContentType QuestionContent { get; set; }**
public string QuestionIdentifier { get; set; }
}
The actual question text goes into the QuestionContent attribute, which is of type "ContentType".
ContentType Class definition from AWS MTurk dotnet API:
public class ContentType
{
public ContentType();
[XmlChoiceIdentifier("ItemsElementName")]
[XmlElement("Application", typeof(ApplicationContentType))]
[XmlElement("Binary", typeof(BinaryContentType))]
[XmlElement("FormattedContent", typeof(String))]
[XmlElement("Text", typeof(String))]
[XmlElement("Title", typeof(String))]
public object[] Items { get; set; }
[XmlElement("ItemsElementName")]
[XmlIgnore]
public ItemsChoiceType[] ItemsElementName { get; set; }
}
I have to move the Actual Question sentence to the [XmlElement("Text", typeof(String))] element of the ContentType object. I dont know the syntax to do that. Please Help.
I was running into the same ValueException error message using the Ruby SDK until I discovered that (unlike ALL the API documentation examples, which showed XML was expected) CreateHit was expecting a Hash, not XML for some parameters (XML for Question, but Hash for QualificationRequirement for example).
In my case it was rejecting a QualificationRequirement when I supplied it XML like they show in the docs, but it worked when I provided it as a Hash.
But since the error message is the same, I suspect that may be what you are running into as well. (The error is language independent... it's not form your SDK, it's what is being returned from AWS when your SDK submits the HIT.)
# does NOT work:
usa_qualification = 'XML STRING LIKE AWS DOCS GIVE'
# DOES work:
usa_qualification = {
:QualificationTypeId => "00000000000000000071",
:Comparator => "EqualTo",
:LocaleValue => { :Country => "US"},
result = mturk.createHIT( :Title => title,
...
:QualificationRequirement => usa_qualification ,
...