Reputation: 215
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": "[email protected]",
"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.
Upvotes: 2
Views: 2917
Reputation: 23078
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);
Upvotes: 2
Reputation: 215
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.
Upvotes: 0
Reputation: 70
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)
Upvotes: 0
Reputation: 522
check if (queryResult.Data.content is Usuario) and cast only if it is true
else cast to string.
Upvotes: 0