I work with web service, and I'm get response from service. Response will be have different structure. For example, API getInfo return response:
{"code":0,"message":"","result":{"nickname":"UserName","age":"22"}}
API signin:
{"code":0,"message":"","result":"user_token"}
etc.
I use HttpWebRequest for POST request. Request and response deserialize/serialize with DataContractSerializer.
So, I want to use factory pattern, this is my implementation:
[DataContract]
public class ResultGetInfo
{
[DataMember(Name = "nickname")]
public int Nickname { get; set; }
[DataMember(Name = "age")]
public int Age { get; set; }
}
[DataContract]
public abstract class Response
{
}
[DataContract]
public class ResponseSignin : Response
{
[DataMember(Name = "code")]
public int Code { get; set; }
[DataMember(Name = "message")]
public string Message { get; set; }
[DataMember(Name = "result")]
public string Result { get; set; }
}
[DataContract]
public class ResponseGetInfo : Response
{
[DataMember(Name = "code")]
public int Code { get; set; }
[DataMember(Name = "message")]
public string Message { get; set; }
[DataMember(Name = "result")]
public ResultGetInfo Result { get; set; }
}
public abstract class CreateResponse
{
public abstract Response CreateResponseObj();
}
public class CreateResponseSignin : CreateResponse
{
public override Response CreateResponseObj()
{
return new ResponseSignin();
}
}
public class CreateResponseGetInfo : CreateResponse
{
public override Response CreateResponseObj()
{
return new ResponseGetInfo();
}
}
I get response in callback function:
private void getResponseCallback(IAsyncResult asynchronousResult)
{
var request = (Request)asynchronousResult.AsyncState;
try
{
HttpWebResponse response;
// End the get response operation
response = (HttpWebResponse)request.HttpRequest.EndGetResponse(asynchronousResult);
Stream streamResponse = response.GetResponseStream();
StreamReader streamReader = new StreamReader(streamResponse);
var gyResponse = streamReader.ReadToEnd();
streamResponse.Close();
streamReader.Close();
response.Close();
Response response_obj = request.Creater.CreateResponseObj();
using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(gyResponse)))
{
var serializer = new DataContractJsonSerializer(response_obj.GetType());
response_obj = (Response)serializer.ReadObject(stream);
if (request.CallBack != null)
{
request.CallBack.Invoke(response_obj, null);
}
}
}
catch (WebException e)
{
if (request.CallBack != null)
{
request.CallBack.Invoke(null, e);
}
}
}
For DataContractJsonSerializer I declare the type here:
var serializer = new DataContractJsonSerializer(response_obj.GetType());
Where response_obj is object, which has needed type (ResponseSignin or ResponseGetInfo).
So, I call Invoke() in my delegate.
private void ResponseHandler(Response result, Exception error)
{
if (error != null)
{
string err = error.Message;
}
else
{
Response response = result;
}
}
And here I have a problem. Variable result really contains correct answer, but I don't get properties, because abstract class Response is without properties. And I can't declare properties or override in derived classes. I'm not sure that chose the desired pattern.
Have you tried just casting the result variable to the type you need?
E.g.
private void ResponseHandler(Response result, Exception error)
{
if (error != null)
{
string err = error.Message;
return;
}
var signInResponse = result as ResponseSignin;
if (signInResponse != null)
{
HandleSignInResponse(signInResponse);
}
var infoResponse = result as ResponseGetInfo;
if (infoResponse != null)
{
HandleInfoResponse(infoResponse);
}
}
Related
I have the following bit of code whihc sends a Http POST request to the server. The server reurns a 400 Bad request response along with a error object in the form of Json:
namespace MyApp.Shared.Dtos.Response
{
public class ErrorItem
{
public string Message { get; set; }
public string Tag { get; set; }
}
public class ErrorDto
{
public string Title { get; set; }
public List<ErrorItem> Errors { get; set; } = new();
}
}
namespace Accounting.Web.Services
{
public interface IHttpService
{
Task<T> Get<T>(string uri);
Task<T> Post<T>(string uri, object value, bool addBearerToken = false);
public ErrorDto Error { get; set; }
}
public class HttpService: IHttpService
{
private HttpClient _httpClient;
public ErrorDto Error { get; set; }
public HttpService(HttpClient httpClient)
{
_httpClient = httpClient;
_stateService = stateService;
}
public async Task<T> Post<T>(string uri, object value)
{
var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new StringContent(JsonSerializer.Serialize(value), Encoding.UTF8, "application/json");
return await sendRequest<T>(request, addBearerToken);
}
private async Task<T> sendRequest<T>(HttpRequestMessage request)
{
using var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
var result = await response.Content.ReadAsStringAsync();
Error = JsonSerializer.Deserialize<ErrorDto>(result);
//..
}
else
{
//..
}
}
}
}
The result correctly recieves the following response from the server as a JSON string:
{"title":"Username or password is incorrect","errors":[]}
And I can confirm by inspecting var result, it has the above value.
However, It doesn't seem deserialize into the ErrorDto class as one would expect it to:
Error = JsonSerializer.Deserialize(result);
But I simply cannot see any problems with the code, it looks like it should be working.
*** UPDATE ***
My server API code returrns the JSOn using the same DTO class (It's a shared class) using the following code:
[HttpPost("authenticate")]
public ActionResult Authenticate(AuthenticateRequest loginRequest)
{
var auth = _userService.Authenticate(loginRequest);
ErrorDto error = new()
{
Title = "Username or password is incorrect"
};
if (auth.user == null || auth.token == null)
{
return BadRequest(error);
}
return Ok(auth.user.ConvertToDto(auth.token));
}
By default System.Text.Json is case-sensitive. There are multiple options to handle this, for example by providing corresponding JsonSerializerOptions:
var json = #"{""title"":""Username or password is incorrect"",""errors"":[]}";
var errorDto = JsonSerializer.Deserialize<ErrorDto>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
Or marking properties with corresponding JsonPropertyNameAttribute:
public class ErrorItem
{
[JsonPropertyName("message")]
public string Message { get; set; }
[JsonPropertyName("tag")]
public string Tag { get; set; }
}
public class ErrorDto
{
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("errors")]
public List<ErrorItem> Errors { get; set; } = new();
}
UPD
From How to customize property names and values with System.Text.Json doc:
Note
The web default is camel case.
If you want to switch from camel case to the naming policy used for DTOs you can do the following:
builder.Services.AddControllers()
.AddJsonOptions(opts => opts.JsonSerializerOptions.PropertyNamingPolicy = null);
I want to return C# class object instead of using JObject in here. Could someone can tell me how to use it.
private async Task<JObject> GetReceiptById(string Id, string name)
{
var response = await _ApiService.Get(Id, name);
var responseStr = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
return JObject.Parse(responseStr);
}
throw new Exception(responseStr);
}
this method is return (return JObject.Parse(responseStr)); below JSON output. for that how to create a new class. I am not sure how to apply all in one class.
{
"receipts": [
{
"ReceiptHeader": {
"Company": "DHSC",
"ErpOrderNum": "730",
"DateTimeStamp": "2022-05-14T13:43:57.017"
},
"ReceiptDetail": [
{
"Line": 1.0,
"ITEM": "PP1016",
"ITEM_NET_PRICE": 0.0
},
{
"Line": 2.0,
"ITEM": "PP1016",
"ITEM_NET_PRICE": 0.0
}
],
"XrefItemsMapping": [],
"ReceiptContainer": [],
"ReceiptChildContainer": [],
"rPrefDO": {
"Active": null,
"AllowLocationOverride": null,
"DateTimeStamp": null
}
}
]
}
You can bind the Response Content to a known Type using ReadAsAsync<T>().
https://learn.microsoft.com/en-us/previous-versions/aspnet/hh835763(v=vs.118)
var result = await response.Content.ReadAsAsync<T>();
In your example you will also run into further issues as you are not closing your response after getting it from the Api Service Get method.
Below is a possible solution where you send your object type to the Get method. (not tested)
public virtual async Task<T> GetApiCall<T>(Id, name)
{
//create HttpClient to access the API
var httpClient = NewHttpClient();
//clear accept headers
httpClient.DefaultRequestHeaders.Accept.Clear();
//add accept json
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//return the client for use
using (var client = await httpClient )
{
//create the response
HttpResponseMessage response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
//create return object
try
{
var result = await response.Content.ReadAsAsync<T>();
//dispose of the response
response.Dispose();
return result;
}
catch
{
throw;
}
}
// do something here when the response fails for example
var error = await response.Content.ReadAsStringAsync();
//dispose of the response
response.Dispose();
throw new Exception(error);
}
}
What you probably looking for is Deserialization
you can achieve it with
var model = JsonConvert.Deserialize<YourClass>(responseStr);
return model;
but the class (YourClass) properties must match the json string you provided in responseStr.
As the comments section you asked for a generated class:
here is what should look like, after you generate the class.
Note: most of the times, you will need to edit the generated class.
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class Receipt
{
public ReceiptHeader ReceiptHeader { get; set; }
public List<ReceiptDetail> ReceiptDetail { get; set; }
public List<object> XrefItemsMapping { get; set; }
public List<object> ReceiptContainer { get; set; }
public List<object> ReceiptChildContainer { get; set; }
public RPrefDO rPrefDO { get; set; }
}
public class ReceiptDetail
{
public double Line { get; set; }
public string ITEM { get; set; }
public double ITEM_NET_PRICE { get; set; }
}
public class ReceiptHeader
{
public string Company { get; set; }
public string ErpOrderNum { get; set; }
public DateTime DateTimeStamp { get; set; }
}
public class Root
{
public List<Receipt> receipts { get; set; }
}
public class RPrefDO
{
public object Active { get; set; }
public object AllowLocationOverride { get; set; }
public object DateTimeStamp { get; set; }
}
generated by: https://json2csharp.com/
I am developing Web API for my client. They have suggestion that all response should be a common JSON structure.
{ Data:"", Status:true, Message:"" }
If error means
{ Error:"", Status:false, Message:"" }
Which is the best method to create a common JSON structure as returns.
Now I created a class having these properties. And created 2 classes from IHttpActionResult,Error.cs and Success.cs, From that the response is created and returned from the controller.
The thing is in my controller,
public IHttpActionResult GetNewsAndAnnouncements()
{
var data = newsAndAnnouncementsDataServices.NewsAndAnnouncements();
if (data != null && data.Count() > 0)
{
return new Success(Request, "News and Announcements Retrieved Successfully", data);
}
return new Error(Request, "No News and Announcements Found");
}
Error.cs
public class Error : IHttpActionResult
{
private readonly string _message;
private readonly HttpRequestMessage _request;
private IErrorResponseModel errorResponse;
public Error(HttpRequestMessage request, string message)
{
_message = message;
_request = request;
errorResponse = new ErrorResponseModel();
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
errorResponse.Message = _message;
errorResponse.Status = false;
errorResponse.Error = _message;
var response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent<object>(errorResponse, new JsonMediaTypeFormatter()),
RequestMessage = _request
};
return Task.FromResult(response);
}
}
Success.cs
public class Success : IHttpActionResult
{
private readonly string _message;
private readonly object _data;
private readonly HttpRequestMessage _request;
private IDataResponseModel dataResponse = new DataResponseModel();
public Success(HttpRequestMessage request, string message, object data)
{
_message = message;
_request = request;
_data = data;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
dataResponse.Message = _message;
dataResponse.Status = true;
dataResponse.Data = _data;
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<object>(dataResponse, new JsonMediaTypeFormatter()),
RequestMessage = _request
};
return Task.FromResult(response);
}
}
DataResponseModel.cs
public class DataResponseModel : Mobility.Common.IDataResponseModel
{
public object Data { get; set; }
public string Message { get; set; }
public bool Status { get; set; }
}
ErrorResponseModel.cs
public class ErrorResponseModel : Mobility.Common.IErrorResponseModel
{
public object Error { get; set; }
public string Message { get; set; }
public bool Status { get; set; }
}
Is this a right method. I need suggestion. Is there any other way to achieve this. I heard about delegating handler something. But I don't have much idea on these.
Please help me.
Another solution to this problem is to hook into the ASP.NET pipeline using custom handlers to build a common response object.
For instance:
[DataContract]
public class ApiResponse
{
[DataMember]
public string Version { get { return "1.2.3"; } }
[DataMember]
public int StatusCode { get; set; }
[DataMember(EmitDefaultValue = false)]
public string ErrorMessage { get; set; }
[DataMember(EmitDefaultValue = false)]
public object Result { get; set; }
public ApiResponse(HttpStatusCode statusCode, object result = null, string errorMessage = null)
{
StatusCode = (int)statusCode;
Result = result;
ErrorMessage = errorMessage;
}
}
Have a look at this post for a reference implementation http://www.devtrends.co.uk/blog/wrapping-asp.net-web-api-responses-for-consistency-and-to-provide-additional-information
I have a method that ts returning Array. I have problem with one class that cant convert the return type to array.
This is my method:
public Model.BaseType[] returnPayment_Gateway()
{
IncomingWebRequestContext request = WebOperationContext.Current.IncomingRequest;
WebHeaderCollection headers = request.Headers;
var settings = new DataContractJsonSerializerSettings { EmitTypeInformation = EmitTypeInformation.Never };
MemoryStream stream1 = new MemoryStream();
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Model.BaseType),settings);
Model.Payment_Gateway[] allRecords = null;
if (headers["ServiceAuthentication"] != null)
{
string ServiceAuthentication = headers["ServiceAuthentication"].ToString();
bool serviceAuth = Service_Authentication(ServiceAuthentication);
DAL.DataManager dal = new DAL.DataManager();
if (serviceAuth == true)
{
allRecords = dal.Get_Payment_Gateway();
}
}
else
{
// Create a new customer to return
return new Model.ReturnResponse() { StatusCode = 201, StatusDescription = "Authentication Fails" };
}
return allRecords;
}
My problem is in else part, not sure how I can convert Model.ReturnResponse() to array now I am getting this error:
cannot implicitly convert type ReturnResponse to Model.BaseType[]
in case you like to see my 3 classes:
This is Base class:
[Serializable]
[DataContract]
[KnownType(typeof(Payment_Gateway))]
[KnownType(typeof(ReturnResponse))]
public class BaseType
{
}
this is Payment_Gateway class:
[DataContract]
public class Payment_Gateway:BaseType
{
[DataMember]
public int Payment_Gateway_ID { get; set; }
[DataMember]
public string Payment_Gateway_Name { get; set; }
[DataMember]
public string Payment_Gateway_URL { get; set; }
[DataMember]
public string Payment_Gateway_Description { get; set; }
and this is ReturnResponse class:
[DataContract]
public class ReturnResponse:BaseType
{
[DataMember]
public int StatusCode { get; set; }
[DataMember]
public string StatusDescription { get; set; }
}
Instead of trying to return a single ReturnResponse:
return new Model.ReturnResponse()
{ StatusCode = 201, StatusDescription = "Authentication Fails" };
Return an array, with the single element in it, since that's what your method is expected to return:
return new[] {
new Model.ReturnResponse()
{ StatusCode = 201, StatusDescription = "Authentication Fails" }
}
Instead of the hierarchy you have designed here, it would be easier if you encapsulate your return data in a response object that indicate a response type (success or failure) and the resulting data.
that class could look like the following
public enum ResponseTypes
{
Success,
Failure
}
public class Response
{
public ResponseTypes ResponseType {get; set;}
public Exception Error {get;set;}
public BaseType[] Data {get;set;}
}
this way it works like an envelope to your actual data and provides state to the caller to know if the call succeeded or not, and maybe for what reason, before reading the data (result)
its the same way WCF acts under the hood (in SOAP, at least) that it surrounds your data contract with a message envelope/enclosure to add more metadata for the caller to interpret the call status.
I am trying to get the total number of questions on stackoverflow for an exercise I am doing and am stuck at the deserialization.
I would be grateful if someone could please let me know what I need to do an maybe provide some example code that would help me.
when I run this up in the console I get the error
"There was an error deserializing the object of type. Encountered unexpected character" which is a downward pointing triangle...
this is what I have so far
using System;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
namespace cs_StackOverflowAPI
{
[DataContract(Namespace = "http://schemas.microsoft.com/search/local/ws/rest/v1")]
public class Question
{
[DataMember(Name = "question_id")]
public int Questions { get; set; }
}
[DataContract]
public class ResourceSet
{
[DataMember(Name = "estimatedTotal")]
public long EstimatedTotal { get; set; }
[DataMember(Name = "resources")]
public Question[] Resources { get; set; }
}
[DataContract]
public class Response
{
[DataMember(Name = "statusCode")]
public int StatusCode { get; set; }
[DataMember(Name = "statusDescription")]
public string StatusDescription { get; set; }
[DataMember(Name = "authenticationResultCode")]
public string AuthenticationResultCode { get; set; }
[DataMember(Name = "errorDetails")]
public string[] errorDetails { get; set; }
[DataMember(Name = "traceId")]
public string TraceId { get; set; }
[DataMember(Name = "resourceSets")]
public ResourceSet[] ResourceSets { get; set; }
}
class Program
{
public string serverUrl = "https://api.stackexchange.com/";
public HttpClient client = null;
static void Main(string[] args)
{
try
{
string noOfQuestions = CreateRequest("questions");
Response noOfQuestionsReponse = MakeRequest(noOfQuestions);
ProcessResponse(noOfQuestionsReponse);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.Read();
}
}
public static string CreateRequest(string queryString)
{
string UrlRequest = "https://api.stackexchange.com/2.2/" +
queryString +
"?&site=stackoverflow";
return (UrlRequest);
}
public static Response MakeRequest(string requestUrl)
{
try
{
HttpWebRequest request = WebRequest.Create(requestUrl) as HttpWebRequest;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception(String.Format(
"Server error (HTTP {0}: {1}).",
response.StatusCode,
response.StatusDescription));
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(Response));
object objResponse = jsonSerializer.ReadObject(response.GetResponseStream());
Response jsonResponse
= objResponse as Response;
return jsonResponse;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return null;
}
}
static public void ProcessResponse(Response noOfQuestionsReponse)
{
// this is where the error occurs
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
public static long ToUnixTime(DateTime date)
{
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return Convert.ToInt64((date.ToUniversalTime() - epoch).TotalSeconds);
}
}
}
It is returning compressed content (gzip, specifically). If possible, tell your http client to automatically decompress it:
request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
request.AutomaticDecompression =
DecompressionMethods.GZip | DecompressionMethods.Deflate;
The next issue is: the schema doesn't match; the questions are in the items of the root:
[DataContract]
public class Response
{
[DataMember(Name ="items")]
List<Question> questions { get;set; }
}