I have faced this problem many times when I want to build a parent-child relationship class.
I have a base AuthenticateRequest class. In my case, I have 2 child requests but they have own logic to GetContent().
It doesn't not really fall into Composite Pattern nor Liskov Substitution as the base method is unique and called.
Which design pattern should I use?
public class AuthenticateRequest
{
public string Url { get; set; }
public string ContentType { get; set; }
public string Method { get; set; }
public virtual HttpContent GetContent()
{
return new StringContent("");
}
}
public class SoapAuthenticateRequest : AuthenticateRequest
{
public string SoapMethodName { get; set; }
public string SoapAction { get; set; }
public string KeyForUserNameParameter { get; set; }
public string ValueForUserNameParameter { get; set; }
public string KeyForPasswordParameter { get; set; }
public string ValueForPasswordParameter { get; set; }
public override HttpContent GetContent()
{
var methodName = this.SoapMethodName;
var keyUsername = this.KeyForUserNameParameter;
var keyPwd = this.KeyForPasswordParameter;
var valueUsername = this.ValueForUserNameParameter;
var valuePwd = this.ValueForPasswordParameter;
var soapAction = this.SoapAction ?? #"http://tempuri.org/";
var soap = $#"<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/""><soap:Body><{methodName} xmlns=""{soapAction}""><{keyUsername}>{valueUsername}</{keyUsername}><{keyPwd}>{valuePwd}</{keyPwd}></{methodName}></soap:Body></soap:Envelope>";
return new StringContent(soap, Encoding.UTF8, ContentTypes.XmlSoap);
}
}
public class JsonAuthenticateRequest : AuthenticateRequest
{
public string SoapMethodName { get; set; }
public string SoapAction { get; set; }
public Dictionary<string, string> ParameterKeyValues { get; set; }
public override HttpContent GetContent()
{
var json = JsonConvert.SerializeObject(ParameterKeyValues);
return new StringContent(json, Encoding.UTF8, ContentTypes.Json);
}
}
public async Task<AuthenticateResponse> Authenicate(AuthenticateRequest request)
{
var requestMsg = new HttpRequestMessage
{
RequestUri = new Uri(request.Url),
Method = new HttpMethod(request.Method.ToString()),
Content = request.GetContent(),
};
var responseMsg = await _httpClient.SendAsync(requestMsg).ConfigureAwait(false);
var responseContent = await responseMsg.Content.ReadAsStringAsync().ConfigureAwait(false);
return new AuthenticateResponse
{
Message = responseContent,
IsSuccess = Regex.Match(responseContent, (string)request.RegexForValidUser).Success
};
}
Did you see Factory pattern?
But for your problem, to require the class having his proper implementation. You can just simply use the abstract keyword like this
public abstract class AuthenticateRequest
{
public abstract HttpContent GetContent();
}
public class SoapAuthenticateRequest : AuthenticateRequest
{
public override HttpContent GetContent()
{
// your logic
return new StringContent(soap, Encoding.UTF8, ContentTypes.XmlSoap);
}
}
public class JsonAuthenticateRequest : AuthenticateRequest
{
public override HttpContent GetContent()
{
var json = JsonConvert.SerializeObject(ParameterKeyValues);
return new StringContent(json, Encoding.UTF8, ContentTypes.Json);
}
}
Related
I am new to Blazor and I am encountering the following issue when trying to post for data with an authentication token : at the time of the API call, an exception is lifted with the message "The JSON value could not be converted to System.Int32. Path: $ | LineNumber: 0 | BytePositionInLine: 1."
Here's the code in my blazor page's code-behind :
public partial class ContactCreate : AuthenticatedPageBase
{
[Inject]
public IContactDataService ContactDataService { get; set; }
[Inject]
public ICountryDataService CountryDataService { get; set; }
public Contact.Post Model { get; set; } = new Contact.Post();
protected string CountryIdString { get; set; } = string.Empty;
protected string TokenString { get; set; } = string.Empty;
public string ErrorMessage { get; set; } = string.Empty;
protected List<Country.ListItem> Countries { get; set; } = new List<Country.ListItem>();
protected async override Task OnInitializedAsync()
{
await base.OnInitializedAsync();
Countries = (await CountryDataService.GetCountryListAsync(Token.Token)).ToList();
TokenString = Token.Token;
}
protected async Task HandleValidSubmit()
{
try
{
Model.CountryId = int.Parse(CountryIdString);
var response = await ContactDataService.PostContactAsync(TokenString, Model);
NavManager.NavigateTo("/contacts");
}
catch(Exception ex)
{
ErrorMessage = ex.Message;
}
}
protected void HandleInvalidSubmit()
{
ErrorMessage = "Le formulaire n'est pas valide. Veuillez réessayer.";
}
}
and here's the relevant code in the data service :
public async Task<int> PostContactAsync(string token, Contact.Post model)
{
var response = await PostAuthenticatedAsync<int>(token, Url, model);
return response;
}
public async Task<T> PostAuthenticatedAsync<T>(string token, string url, object model)
{
var jsonBody = model.ToJson();
var request = new HttpRequestMessage()
{
RequestUri = new Uri(HttpClient.BaseAddress.ToString() + url),
Method = HttpMethod.Post,
Content = jsonBody
};
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
var response = await HttpClient.SendAsync(request);
return await response.FromJson<T>(Options);
}
...and the extension method that serializes the object into json :
public static StringContent ToJson(this object o)
{
return new StringContent(JsonSerializer.Serialize(o), Encoding.UTF8, "application/json");
}
Here's the object model that I'm passing through :
public class Contact
{
public class Post
{
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(50)]
public string CompanyName { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
[MaxLength(20)]
public string PostCode { get; set; }
[MaxLength(60)]
public string Locality { get; set; }
public int CountryId { get; set; }
}
}
And, finally, here's the API method that I'm trying to reach :
[HttpPost]
public async Task<ActionResult> PostContact(Contact.Post model)
{
try
{
var createdId = await _contactRepository.CreateAsync(model);
return Ok(new { Id = createdId });
}
catch (Exception ex)
{
return BadRequest(new { ex.Message });
}
}
Any idea what is happening or what actual exception lies behind this cryptic error message ?
P.S. : I know that there is a question with that exact exception message but it concerns .NET Core while I'm targeting .NET Standard 2.1. I've read it and it visibly doesn't apply to this case.
You are not returning an int (the Id). You're returning an anonymous object with an int property named Id.
Try
return Ok(createdId);
When I get a json response from HttpClient () and try to deselize, my Xamarin application freezes (UI works, but the code after in class ExecuteGetRequest line 15 does not work). What can it be because of?
No errors.
I call the method of obtaining a list of anime user
ShikimoriMain shikimoriMain = new ShikimoriMain();
var UserInformation = await shikimoriMain.GetUserInformation(Convert.ToInt64(UserID));
var UserAnimeList = await shikimoriMain.GetUserAnimeList(Convert.ToInt64(UserID), 1, 5);
string animeName = UserAnimeList.Anime[0].Anime.Name;
ShikimoriMain.GetUserAnimeList
public async Task<ShikimoriUserAnimeList> GetUserAnimeList(long id, int page, int limit)
{
string[] args = new string[] { ShikimoriCategories.UserID + "/" + id + ShikimoriCategories.UserAnimeList + $"?limit={limit}&page={page}" };
return await ExecuteGetRequest<ShikimoriUserAnimeList>(args);
}
ExecuteGetRequest
public async Task<T> ExecuteGetRequest<T>(string[] args) where T : class
{
T returnedObject;
using (var client = new HttpClient())
{
// client.BaseAddress = new Uri($"{httpApiv1}/{args}");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, httpApiv1 + String.Join("/", args));
request.Headers.TryAddWithoutValidation("User-Agent", "Search Anime");
HttpResponseMessage responseMessage = await client.SendAsync(request);
string json = await responseMessage.Content.ReadAsStringAsync(); // successfully get json
returnedObject = JsonConvert.DeserializeObject<T>(json); // after that the code is not executed
return returnedObject;
}
}
ShikimoriUserAnimeList
public class ShikimoriUserAnimeList
{
[JsonProperty()]
public List<GetAnime> Anime { get; set; }
}
public class GetAnime
{
[JsonProperty("id")]
public int ID { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("anime")]
public Anime Anime { get; set; }
}
public class Anime
{
[JsonProperty("id")]
public int ID { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("russian")]
public string NameRU { get; set; }
[JsonProperty("image")]
public AnimeImage AnimeImage { get; set; }
[JsonProperty("kind")]
public string King { get; set; }
[JsonProperty("score")]
public string Score { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("episodes")]
public int Episodes { get; set; }
}
public class AnimeImage
{
[JsonProperty("original")]
public string Original { get; set; }
[JsonProperty("preview")]
public string Preview { get; set; }
[JsonProperty("x96")]
public string ImageX96 { get; set; }
[JsonProperty("x48")]
public string ImageX48 { get; set; }
}
For the sake of completion:
An error was being thrown but was not visible in the device log. Wrapping the JsonConvert.DeserializeObject<T>(json) in a try catch block helped finding the Exceptionbeing thrown.
try
{
returnedObject = JsonConvert.DeserializeObject<T>(json); // after that the code is not executed
return returnedObject;
}
catch (Exception ex)
{
... (debug and fix the error that occurred)
}
I had same problem, I've realized that using HttpClient async will cause deadlock in winforms or xamarin (however it works well with Asp) and I changed these lines
HttpResponseMessage responseMessage = await client.SendAsync(request);
string json = await responseMessage.Content.ReadAsStringAsync(); // successfully get json
Like these (Make them work synchronous):
HttpResponseMessage responseMessage = client.SendAsync(request).Result;
string json = responseMessage.Content.ReadAsStringAsync().Result; // successfully get json
And change your method as default synchronous
Take a look at Here
I am trying to call an API with HttpClient and deserialize the json to a response object. In this json, there is a list of trivia question objects. When I set the object to the deserialized object, the list stays empty.
I've checked if HttpClient works, it does, I also tried using JsonConvert.
These are the TriviaQuestion and Response classes:
public class TriviaQuestion
{
public string Category { get; set; }
public string Type { get; set; }
public string Difficulty { get; set; }
public string Question { get; set; }
public string CorrectAnswer { get; set; }
public List<string> IncorrectAnswers { get; set; }
public override string ToString()
{
return $"Question: {Question}";
}
}
public class Response
{
public int ResponseCode { get; set; }
public List<TriviaQuestion> Questions { get; set; }
public Response()
{
Questions = new List<TriviaQuestion>();
}
}
This is the code for deserializing
private static HttpClient client = new HttpClient();
private static string URL = "https://opentdb.com/api.php";
private static string urlParameters = "?amount=1";
static void Main()
{
RunAsync().GetAwaiter().GetResult();
}
static async Task RunAsync()
{
client.BaseAddress = new Uri(URL);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
Response response = new Response();
try
{
response = await GetResponseAsync(urlParameters);
ShowResponse(response);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}
static async Task<Response> GetResponseAsync(string path)
{
Response response = new Response();
//string responseString = "";
HttpResponseMessage httpResponse = await client.GetAsync(path);
if (httpResponse.IsSuccessStatusCode)
{
//responseString = httpResponse.Content.ReadAsStringAsync().Result;
response = await httpResponse.Content.ReadAsAsync<Response>();
}
//response = JsonConvert.DeserializeObject<Response>(responseString);
return response;
}
I expect to get a list of trivia question objects, but the list stays on count = 0. If I print out the jsonString I'm getting this is the result:
{
"response_code":0,
"results": [
{
"category":"Entertainment: Video Games",
"type":"multiple",
"difficulty":"medium",
"question":"In Need for Speed: Underground, what car does Eddie drive?",
"correct_answer":"Nissan Skyline GT-R (R34)",
"incorrect_answers": [
"Mazda RX-7 FD3S",
"Acura Integra Type R",
"Subaru Impreza 2.5 RS"
]
}]
}
Thanks for helping!
Your Response class is slightly wrong. It does not match the JSON you posted.
public List<TriviaQuestion> Questions { get; set; }
should be:
public List<TriviaQuestion> Results { get; set; }
Additionally, as your JSON has snake casing, to capture the response_code, correct_answer and incorrect_answers values you will need to either decorate your class properties with JsonProperty attributes i.e. [JsonProperty(PropertyName = "incorrect_answers")] or you can use a ContractResolver:
var contractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
};
var response = JsonConvert.DeserializeObject<Response>(json, new JsonSerializerSettings
{
ContractResolver = contractResolver
});
So your full classes would be:
public class TriviaQuestion
{
public string Category { get; set; }
public string Type { get; set; }
public string Difficulty { get; set; }
public string Question { get; set; }
// only need this if not using the ContractResolver
[JsonProperty(PropertyName = "correct_answer")]
public string CorrectAnswer { get; set; }
// only need this if not using the ContractResolver
[JsonProperty(PropertyName = "incorrect_answers")]
public List<string> IncorrectAnswers { get; set; }
}
public class Response
{
// only need this if not using the ContractResolver
[JsonProperty(PropertyName = "response_code")]
public int ResponseCode { get; set; }
public List<TriviaQuestion> Results { get; set; }
}
Then you will be able to deserialize:
var json = "{\r\n \"response_code\":0,\r\n \"results\": [\r\n { \r\n \"category\":\"Entertainment: Video Games\",\r\n \"type\":\"multiple\",\r\n \"difficulty\":\"medium\",\r\n \"question\":\"In Need for Speed: Underground, what car does Eddie drive?\",\r\n \"correct_answer\":\"Nissan Skyline GT-R (R34)\",\r\n \"incorrect_answers\": [\r\n \"Mazda RX-7 FD3S\",\r\n \"Acura Integra Type R\",\r\n \"Subaru Impreza 2.5 RS\"\r\n ]\r\n }]\r\n}";
// if using JsonProperty attributes
var response = JsonConvert.DeserializeObject<Response>(json);
// or
// if using ContractResolver
var contractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
};
var response = JsonConvert.DeserializeObject<Response>(json, new JsonSerializerSettings
{
ContractResolver = contractResolver
});
the returned JSON into a Response:
var json = await httpResponse.Content.ReadAsStringAsync();
response= JsonConvert.DeserializeObject<Response>(json);
I have used custom InputFormatters for creating a subset of request from the generic request that request body receives in API request.
var reqModel = new XmlSerializer(CurrentType).Deserialize(xmlDoc.CreateReader());
SubSetRequest model = ConvertToSubSetRequestObject(reqModel as BigRequest);
return InputFormatterResult.Success(model);
Now in controller ModelState.IsValid is not pointing to SubSetRequest but to BigRequest, which I have received request body
public ActionResult<object> Calculate(SubSetRequest request)
{
if (!ModelState.IsValid){ }
// other codes..
}
Any idea how can we validate ModelState against SubSetRequest type.
Important Classes :
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore(options =>
{
options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
options.InputFormatters.Insert(0, new XMLDocumentInputFormatter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters();
}
BigRequest.cs
[System.SerializableAttribute()]
public class BigRequest
{
public string Name { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public string Company { get; set; }
public string Designation { get; set; }
public string CompanyAddress { get; set; }
}
SubSetRequest.cs
[System.SerializableAttribute()]
public class SubSetRequest
{
public string Name { get; set; }
[Required] //This should tiger **Validation** error
public string Email { get; set; }
public string Address { get; set; }
}
XMLDocumentInputFormatter.cs
internal class XMLDocumentInputFormatter : InputFormatter
{
private Type CurrentType { get; set; }
public XMLDocumentInputFormatter()
{
SupportedMediaTypes.Add("application/xml");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
using (var streamReader = new StreamReader(context.HttpContext.Request.Body))
{
CurrentType = typeof(BigRequest);
var xmlDoc = await XDocument.LoadAsync(streamReader, LoadOptions.None, CancellationToken.None);
var reqModel = new XmlSerializer(CurrentType).Deserialize(xmlDoc.CreateReader());
var model = ConvertToSubSetRequestObject(reqModel as BigRequest);
return InputFormatterResult.Success(model);
}
}
public SubSetRequest ConvertToSubSetRequestObject(BigRequest request)
{
var retObject = new SubSetRequest
{
Name = request.Name,
Address = request.Address
};
return retObject;
}
}
ValueController.cs
[HttpPost]
[Route("/api/Value/Calculate")]
public virtual ActionResult<object> Calculate(SubSetRequest request)
{
TryValidateModel(request);
if (ModelState.IsValid) // is giving as TRUE, even if EMAIL is NULL
{
var context = new ValidationContext(request, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
// this is working properly
var isValid = Validator.TryValidateObject(request, context, results);
}
return new ActionResult<object>(request.ToString());
}
Im trying to deserialize an xml response from from Amazons simple db service.
The xml is like this.
<ListDomainsResponse xmlns="http://sdb.amazonaws.com/doc/2009-04-15/">
<ListDomainsResult>
<DomainName>Logging</DomainName>
<DomainName>teets</DomainName>
</ListDomainsResult>
<ResponseMetadata>
<RequestId>9d48122e-1ddf-8771-b771-b36599838ea0</RequestId>
<BoxUsage>0.0000071759</BoxUsage>
</ResponseMetadata>
</ListDomainsResponse>
And the object i'm trying to serialize into is this.
public class ListDomainsResponse : Response
{
public ListDomainsResult ListDomainsResult { get; set; }
}
public class ListDomainsResult
{
public List<String> DomainNames { get; set; }
public string NextToken { get; set; }
}
public class Response
{
public static string NameSpace = "http://sdb.amazonaws.com/doc/2009-04-15/";
public ResponseMetadata ResponseMetadata{ get; set; }
}
public class ResponseMetadata
{
public string RequestId { get ; set; }
public string BoxUsage { get; set; }
}
My problem is that the response metadata is getting deserialized correctly but the List of strings called DomainName is not being deserialized, it is empty each time.
Can someone see what i'm doing wrong.
Updated with more code
public async Task<Response> ListDomains (ListDomainsRequest request)
{
using (Client = new HttpClient ()) {
ListDomainsRequestMarshaller marshaller = new ListDomainsRequestMarshaller ();
marshaller.Configure (request);
HttpResponseMessage responseMessage = Client.GetAsync (marshaller.Marshal ()).Result;
ListDomainsResponseUnMarshaller unmarshaler = new ListDomainsResponseUnMarshaller (responseMessage);
return unmarshaler.Response;
}
}
public ListDomainsResponseUnMarshaller (HttpResponseMessage message)
{
XDocument doc = XDocument.Load (message.Content.ReadAsStreamAsync ().Result);
if (message.StatusCode.Equals (HttpStatusCode.OK)) {
Serializer = new XmlSerializer (typeof(ListDomainsResponse), Response.NameSpace);
Response = (ListDomainsResponse)Serializer.Deserialize (doc.CreateReader ());
Response.HttpStatusCode = message.StatusCode;
Response.ContentLength = (long)message.Content.Headers.ContentLength;
} else {
Serializer = new XmlSerializer (typeof(Response));
Response = (Response)Serializer.Deserialize (doc.CreateReader ());
Response.HttpStatusCode = message.StatusCode;
Response.ContentLength = (long)message.Content.Headers.ContentLength;
}
Serializer = null;
}
Adding Attributes helped
[System.Xml.Serialization.XmlTypeAttribute (Namespace = "http://sdb.amazonaws.com/doc/2009-04-15/")]
[System.Xml.Serialization.XmlRootAttribute ("ListDomainsResponse", Namespace = "http://sdb.amazonaws.com/doc/2009-04-15/")]
public class ListDomainsResponse : Response
{
public ListDomainsResult ListDomainsResult { get; set; }
}
[System.Xml.Serialization.XmlTypeAttribute (Namespace = "http://sdb.amazonaws.com/doc/2009-04-15/")]
[System.Xml.Serialization.XmlRootAttribute (Namespace = "http://sdb.amazonaws.com/doc/2009-04-15/")]
public class ListDomainsResult
{
[System.Xml.Serialization.XmlElementAttribute ("DomainName")]
public string[] DomainName { get; set; }
public string NextToken { get; set; }
}