C#. How can I pass my json deserializing method as Async? - c#

So here's what I am trying to do and what I have accomplished.
private static T _getjson<T>(string url) where T : new()
{
using (var w = new WebClient())
{
var json_data = string.Empty;
// attempt to download JSON data as a string
try
{
json_data = w.DownloadString(url);
}
catch (Exception) { }
// if string with JSON data is not empty, deserialize it to class and return its instance
return !string.IsNullOrEmpty(json_data) ? JsonConvert.DeserializeObject<T>(json_data) : new T();
}
}
This method (when called) is used like so:
var onlineornot = ("http://blah.com");
var chatters = _getjson<Rootobject>(onlineornot);
<Rootobject> being a class set up like this:
public class Rootobject
{
public _Links _links { get; set; }
public int chatter_count { get; set; }
public Chatters chatters { get; set; }
public Stream stream { get; set; }
public Stream game { get; set; }
public _Links2 _links2 { get; set; }
}
For the most part, it works but it causes my app to hang every time I call _getJson. I was wondering how I could use Async in this case, while maintaining the ability to get the properties from <Rootobject>.

The WebClient class has a DownloadStringAsync() method (doc) you can use.
Here is a brief blog post that shows how you can use async/await to prevent your UI from blocking.
ex:
private static async Task<T> _getjson<T>(string url) where T : new()
{
using (var w = new WebClient())
{
var json_data = string.Empty;
// attempt to download JSON data as a string
try
{
json_data = await w.DownloadStringTaskAsync(url);
}
catch (Exception) { }
// if string with JSON data is not empty, deserialize it to class and return its instance
return !string.IsNullOrEmpty(json_data) ? JsonConvert.DeserializeObject<T>(json_data) : new T();
}
}
public async void Button1_Click(...)
{
...
var onlineornot = ("http://example.com");
var chatters = await _getjson<Rootobject>(onlineornot);
...
}

Quick answer, use WebClient.DownloadStringAsync method: https://msdn.microsoft.com/en-us/library/ms144202(v=vs.110).aspx
Modify your code like this:
private static async Task<T> _getjson<T>(string url) where T : new()
{
using (var w = new WebClient())
{
var json_data = string.Empty;
// attempt to download JSON data as a string
try
{
json_data = await w.DownloadStringTaskAsync(new Uri(url));
}
catch (Exception) { }
// if string with JSON data is not empty, deserialize it to class and return its instance
return !string.IsNullOrEmpty(json_data) ? JsonConvert.DeserializeObject<T>(json_data) : new T();
}
}
Also, the key thing here is to make sure this is not executed on the main UI thread.

Related

JSON string will not deserialize into the type specified

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

WCF Service return conditional object type

I have this controller
public ActionResult Retrieve()
{
ServiceResponse resp = service.GetModel();
if(resp.model == null)
{
return Json(resp.model);
}
return Json(new { Messages = resp.Messages, Data = respmodel, Stream = resp.Stream});
}
I want to remove conditional json in controller so controller just return like this
public ActionResult Retrieve()
{
ServiceResponse resp = service.GetModel();
return Json(resp.Result);
}
But MyModel and new { Messages = resp.Messages, Data = respmodel, Stream = resp.Stream} is different type of object. I want to declare it as 1 data member.
I already try this
[DataContract]
public class ServiceResponse
{
[DataMember]
public object Result { get; set; }
}
But it gives me an error. I think I can't use object in DataContract. I also tried to add [KnownType(MyModel)].
But it doesn't work.

Posting a complex object with ASP.NET WebAPI

How can I use WebClient object to send a POST request like this:
public static void SaveOrUpdateEntity(string url, object data)
{
using (var client = new WebClient())
{
// TODO
}
}
where its data is a Person object.
This is controller method
[HttpPost]
public void Post([FromBody]Person person)
{
VeranaWebService.SaveOrUpdatePerson(person);
}
and Person class
public class Person
{
public string Name { get; set; }
public string FirstName { get; set; }
public DateTime? BirthDate { get; set; }
public byte[] Photo { get; set; }
}
You can use Newtonsoft.Json which will help you serialize your data to a json object. It can be used like this
using Newtonsoft.Json;
public static void SaveOrUpdateEntity(string url, object data)
{
var dataString = JsonConvert.SerializeObject(data);
using (var client = new WebClient())
{
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
response = client.UploadString(new Uri(url), "POST", dataString);
}
}
To learn more about the newtonsoft library, read here

Serializing multiple Objects

class AppDataManager
{
public static async Task SaveAsync<T>(T data, string fileName)
{
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName,
CreationCollisionOption.GenerateUniqueName);
var stream = await file.OpenStreamForWriteAsync();
var serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(stream ,data);
await stream.FlushAsync();
}
public static async Task<T> RestoreAsync<T>(string fileNa me)
{
try
{
var file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
var instream = await file.OpenStreamForReadAsync();
var serializer = new DataContractSerializer(typeof(T));
return (T)serializer.ReadObject(instream);
}
catch (Exception)
{
return default(T);
}
}
}
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
await AskUserToLocalDataAsync();
if (useLocalData)
{
SomethingGoesHere = await AppDataManager.RestoreAsync<UserData>(fileName);
}
}
the code works fine, but it return only one Object, I want to write multiple objects and retrieve it as an observableCollection to bind it to a GridView. the GenerateUniqueName is to append multiple objects wright ?
how to store it ? and how to retrieve it ?
Create a class with a property that will hold your collection. Then serialize that class.
I would recommend creating a Collection class for your objects.
For example a Widget object:
[DataContract]
public class Widget
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Value { get; set; }
}
You could then make a WidgetCollection class:
[DataContract]
public class WidgetCollection
{
[DataMember]
public List<Widget> Widgets { get; set; }
}
You can serialize/deserialize the WidgetCollection and then foreach through and add to an ObservableCollection<Widget>.
Also, wrap your IDisposable objects (like IRandomAccessStream) in using statements so they get properly disposed.

Problems with implementation of the factory pattern

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

Categories