jgiacomini / Tiny.RestClient

Simpliest Fluent REST client for .NET
MIT License
210 stars 30 forks source link

Deserialize by HttpStatus #110

Open auriou opened 4 years ago

auriou commented 4 years ago

Would it be possible to deserialize the return object compared to the http status. Example of signature of one of my methods

[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(LoginAuthModel))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorModel))]
[Consumes("application/json")]
public async Task<IActionResult> Login([FromBody] LoginRequest model)
jgiacomini commented 4 years ago

Hello,

By now we can easily catch exception by status code like below :

try
{ 
   var response = await client.GetRequest("City").ExecuteAsync<City>();
}
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) // 404
{
    // DO custom deserialisation here   
}
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.InternalServerError) // 500
{
      //DO custom deserialisation here
}

It misses only the way to deserialize automatically by each status code!

If you have any suggestion to implement it.

auriou commented 4 years ago

Exceptions are often very resource intensive. A write like this by manipulating HttpResponseMessage, and not putting EnsureSuccessStatusCode. To later manipulate the HttpStatus codes for deserialization of each type

await client.
    GetRequest("GetTest/complex")
    .AddResponseByStatus<TestClass>(HttpStatusCode.OK, p => Debug.WriteLine(p))
    .AddResponseByStatus<ErrorClass>(HttpStatusCode.BadRequest, p => Debug.WriteLine(p))
    .ExecuteByStatusAsync();
jgiacomini commented 4 years ago

@auriou je viens de voir ton message avec quelques mois de retard et je m'en excuse ! Dans ton exemple tu voudrai avoir le résultat dans les call back pour chaque status ?

L'implémentation actuelle je trouve colle pas mal à la notion d'exception :

De ce fait, le surcoût généré par l'exception est amoindri je trouve. :)

auriou commented 4 years ago

Hi @jgiacomini , pareil de mon coté j'ai tardé un peut mais pas tellement de temps.

Je travaille actuellement sur mon temps libre sur un projet Xamarin, et j'ai implémenté le code suivant en reprenant tes concepts, je pourrais te faire une pullrequest quand j'aurais le temps si ca t'intéresse, mais actuellement c est dans une lib a part, incluant du cache avec LiteDb et l'utilisation des Etags, et les modificateur d'entêtes.

Avec l'utilisation des try/catch je trouve que l'api fluent perd de sa lisibilité.

Voila mon code, si cela t'intéresse, comme cela dans le Fail je peux avoir le statusHttp voir d'autres éléments de la réponse et aussi mon model d'erreur de on API. J'ai fait une autre méthode pour avoir juste la response http

Mon test

[TestMethod]
public async Task Error()
{
    ErrorModel myError = null;
    var client = ClientLogin();
    var result1 = await client.GetRequest("product/error")
        .AddFail<ErrorModel>((httpResponseMessage, errorModel) => { myError = errorModel; })
        .ExecuteAsync<List<ProductModel>>();

    myError.Should().NotBeNull();

    var status = 0;
    var result2 = await client.GetRequest("product/error")
        .AddFail(httpResponseMessage => { status = (int)httpResponseMessage.StatusCode; })
        .ExecuteAsync<List<ProductModel>>();

    status.Should().BeGreaterThan(299);
    await Task.CompletedTask;
}

Mon code dans le constructeur de requetes

private dynamic _failAction;
private Action<HttpResponseMessage> _failHttpReponseAction;
internal dynamic FailAction => _failAction;
internal Action<HttpResponseMessage> FailHttpReponseAction => _failHttpReponseAction;

public HttpProviderRequest AddFail<TError>(Action<HttpResponseMessage, TError> errorAction)
{
    _failAction = errorAction;
    return this;
}

public HttpProviderRequest AddFail(Action<HttpResponseMessage> errorAction)
{
    _failHttpReponseAction = errorAction;
    return this;
}

Et le code dans le SendAsync

if (!response.IsSuccessStatusCode)
{
    if (requestHttp.FailHttpReponseAction != null)
    {
        requestHttp.FailHttpReponseAction(response);
        return null;
    }
    if (requestHttp.FailAction != null)
    {
        var errorType = requestHttp.FailAction.Method.DeclaringType.GetFields()[0].FieldType;
        var serialized = await response.Content.ReadAsStringAsync();
        var error = JsonConvert.DeserializeObject(serialized, errorType, _serializerSettings);
        var errorInType = Convert.ChangeType(error, errorType);
        requestHttp.FailAction(response, errorInType);
        return null;
    }
}

Et il faut référencer Microsoft.CSharp pour l'utilisation de dynamic