web-dev-qa-db-fra.com

Conversion du contenu de HttpResponseMessage en objet

Ma question: Comment puis-je faire cela?

Donc, je n'avais touché à rien. Net depuis environ 6 ans jusqu'à cette semaine. Il y a beaucoup de choses que j'ai oubliées et encore plus que je n'ai jamais connues. Bien que j'aime l'idée des mots clés async/wait, j'ai un léger problème à mettre en œuvre les exigences suivantes pour la mise en œuvre de l'API d'un client:

  1. La classe ServerAPI a une méthode pour chacune des méthodes API, en prenant les paramètres d'entrée appropriés (par exemple, la méthode Login prend en une id et une password, effectue l'appel API et renvoie le résultat à l'appelant).
  2. Je souhaite faire abstraction du code JSON afin que mes méthodes d'API renvoient l'objet réel que vous extrayez (par exemple, la méthode Login ci-dessus renvoie un objet User avec votre jeton d'authentification, uid, etc.).
  3. Certaines méthodes de l'API renvoient un 204 en cas de succès ou aucun contenu significatif (cela n'a pas de sens dans mon cas d'utilisation, je me soucie peut-être uniquement du succès/de l'échec). Pour celles-ci, j'aimerais renvoyer soit un bool (true = success), soit le code d'état.
  4. J'aimerais conserver le design asynchrone/wait (ou équivalent), car il semble bien fonctionner jusqu'à présent.
  5. Pour certaines méthodes, il se peut que je doive simplement renvoyer l'objet HttpResponseMessage et laisser l'appelant s'en charger.

C’est à peu près ce que j’ai jusqu’à présent et je ne sais pas comment le rendre conforme à ce qui précède OR si je le fais même bien. Tous les conseils sont appréciés (mais pas la flamme).

// 200 (+User JSON) = success, otherwise APIError JSON
internal async Task<User> Login (string id, string password)
{
    LoginPayload payload = new LoginPayload() { LoginId = id, Password = password};
    var request = NewRequest(HttpMethod.Post, "login");
    JsonPayload<LoginPayload>(payload, ref request);

    return await Execute<Account>(request, false);
}

// 204: success, anything else failure
internal async Task<Boolean> LogOut ()
{
    return await Execute<Boolean>(NewRequest(HttpMethod.Delete, "login"), true);
}

internal async Task<HttpResponseMessage> GetRawResponse ()
{
    return await Execute<HttpResponseMessage>(NewRequest(HttpMethod.Get, "raw/something"), true);
}

internal async Task<Int32> GetMeStatusCode ()
{
    return await Execute<Int32>(NewRequest(HttpMethod.Get, "some/intstatus"), true);
}

private async Task<RESULT> Execute<RESULT>(HttpRequestMessage request, bool authenticate)
{
    if (authenticate)
        AuthenticateRequest(ref request); // add auth token to request

    var tcs = new TaskCompletionSource<RESULT>();
    var response = await client.SendAsync(request);     

    // TODO: If the RESULT is just HTTPResponseMessage, the rest is unnecessary        

    if (response.IsSuccessStatusCode)
    {
        try
        {
            // TryParse needs to handle Boolean differently than other types
            RESULT result = await TryParse<RESULT>(response);
            tcs.SetResult(result);
        }
        catch (Exception e)
        {
            tcs.SetException(e);
        }

    }
    else
    {
        try
        {
            APIError error = await TryParse<APIError>(response);
            tcs.SetException(new APIException(error));
        }
        catch (Exception e)
        {
            tcs.SetException(new APIException("Unknown error"));
        }
    }

    return tcs.Task.Result;
}

Il s’agit de la structure JSON APIError (c’est le code de statut + un code d’erreur personnalisé). 

{
  "status": 404,
  "code":216,
  "msg":"User not found"
}

Je préférerais rester avec System.Net, mais c'est principalement parce que je ne veux pas basculer tout mon code. Si ce que je veux est plus facile à réaliser autrement, cela vaut évidemment la peine de travailler davantage.

Merci.

5
copolii

Donc, d’abord pour répondre au vous avez besoin de Newtonsoft.Jsoncommentaires, je n’en ai pas vraiment ressenti le besoin. J'ai trouvé que le support intégré fonctionnait bien jusqu'à présent (en utilisant le APIError Json dans ma question initiale:

[DataContract]
internal class APIError
{
    [DataMember (Name = "status")]
    public int StatusCode { get; set; }
    [DataMember (Name = "code")]
    public int ErrorCode { get; set; }
}

J'ai également défini une classe JsonHelper pour (dé) sérialiser:

public class JsonHelper
{
    public static T fromJson<T> (string json)
    {
        var bytes = Encoding.Unicode.GetBytes (json);

        using (MemoryStream mst = new MemoryStream(bytes))
        {
            var serializer = new DataContractJsonSerializer (typeof (T));
            return (T)serializer.ReadObject (mst);
        }
    }

    public static string toJson (object instance)
    {
        using (MemoryStream mst = new MemoryStream())
        {
            var serializer = new DataContractJsonSerializer (instance.GetType());
            serializer.WriteObject (mst, instance);
            mst.Position = 0;

            using (StreamReader r = new StreamReader(mst))
            {
                return r.ReadToEnd();
            }
        }
    }
}

Les bits ci-dessus que j'avais déjà de travail. Quant à une méthode unique qui gèrerait chaque exécution de requête en fonction du type de résultat attendu, tout en facilitant change la façon dont je gère les choses (comme les erreurs, etc.), cela ajoute également à la complexité et donc lisibilité de mon code. J'ai fini par créer des méthodes séparées (toutes les variantes de la méthode Execute dans la question d'origine:

// execute and return response.StatusCode
private static async Task<HttpStatusCode> ExecuteForStatusCode (HttpRequestMessage request, bool authenticate = true)
// execute and return response without processing
private static async Task<HttpResponseMessage> ExecuteForRawResponse(HttpRequestMessage request, bool authenticate = true)
// execute and return response.IsSuccessStatusCode
private static async Task<Boolean> ExecuteForBoolean (HttpRequestMessage request, bool authenticate = true)
// execute and extract JSON payload from response content and convert to RESULT 
private static async Task<RESULT> Execute<RESULT>(HttpRequestMessage request, bool authenticate = true)

Je peux déplacer les réponses non autorisées (ce que mon code actuel ne gère pas actuellement) dans une nouvelle méthode CheckResponse qui déconnectera (par exemple) l'utilisateur si un 401 est reçu.

0
copolii

Voici un exemple de la façon dont je l’ai fait en utilisant MVC API 2 en tant que serveur principal. Mon backend renvoie un résultat JSON si les informations d'identification sont correctes. UserCredentials class est exactement le même modèle que le résultat json.Vous devrez utiliser System.Net.Http.Formatting qui se trouve dans le Microsoft.AspNet.WebApi.Client NugetPackage

public static async Task<UserCredentials> Login(string username, string password)
{
    string baseAddress = "127.0.0.1/";
    HttpClient client = new HttpClient();

    var authorizationHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes("xyz:secretKey"));
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authorizationHeader);



    var form = new Dictionary<string, string>
    {
        { "grant_type", "password" },
        { "username", username },
        { "password", password },
    };

    var Response = await client.PostAsync(baseAddress + "oauth/token", new FormUrlEncodedContent(form));
    if (Response.StatusCode == HttpStatusCode.OK)
    {
        return await Response.Content.ReadAsAsync<UserCredentials>(new[] { new JsonMediaTypeFormatter() });
    }
    else
    {
        return null;
    }
}

et vous avez également besoin du paquet Newtonsoft.Json.

public class UserCredentials
    {
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }

    [JsonProperty("token_type")]
    public string TokenType { get; set; }

    [JsonProperty("expires_in")]
    public int ExpiresIn { get; set; }

    //more properties...
    }
5
Stamos