Décompression du flux GZip à partir de la réponse HTTPClient

93

J'essaie de me connecter à une api, qui retourne du JSON codé GZip, à partir d'un service WCF (service WCF vers service WCF). J'utilise le HTTPClient pour me connecter à l'API et j'ai pu renvoyer l'objet JSON sous forme de chaîne. Cependant, je dois pouvoir stocker ces données retournées dans une base de données et, en tant que tel, j'ai pensé que le meilleur moyen serait de retourner et de stocker l'objet JSON dans un tableau ou un octet ou quelque chose du genre.

Ce qui me pose des problèmes en particulier, c'est la décompression de l'encodage GZip et j'ai essayé de nombreux exemples différents, mais je n'arrive toujours pas à l'obtenir.

Le code ci-dessous explique comment j'établis ma connexion et j'obtiens une réponse, c'est le code qui renvoie une chaîne de l'API.

public string getData(string foo)
{
    string url = "";
    HttpClient client = new HttpClient();
    HttpResponseMessage response;
    string responseJsonContent;
    try
    {
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        response = client.GetAsync(url + foo).Result;
        responseJsonContent = response.Content.ReadAsStringAsync().Result;
        return responseJsonContent;
    }
    catch (Exception ex)
    {
        System.Windows.Forms.MessageBox.Show(ex.Message);
        return "";
    }
}

J'ai suivi quelques exemples différents comme ces API StackExchange , MSDN et sur stackoverflow, mais je n'ai pas réussi à faire fonctionner l'un de ces derniers.

Quelle est la meilleure façon d'y parvenir, suis-je même sur la bonne voie?

Merci les gars.

Corey
la source
"le meilleur moyen serait de retourner et de stocker l'objet JSON dans un tableau ou un octet" Notez qu'une chaîne est un tableau d'octets.
user3285954

Réponses:

232

Instanciez simplement HttpClient comme ceci:

HttpClientHandler handler = new HttpClientHandler()
{
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};

using (var client = new HttpClient(handler))
{
    // your code
}

Mise à jour du 19 juin 2020: il n'est pas recommandé d'utiliser httpclient dans un bloc `` using '' car cela pourrait entraîner l'épuisement du port.

private static HttpClient client = null;

ContructorMethod()
{
   if(client == null)
   {
        HttpClientHandler handler = new HttpClientHandler()
        {
            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
        };        
        client = new HttpClient(handler);
   }
// your code            
 }

Si vous utilisez .Net Core 2.1+, pensez à utiliser IHttpClientFactory et à injecter comme ceci dans votre code de démarrage.

 var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
            TimeSpan.FromSeconds(60));

 services.AddHttpClient<XApiClient>().ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
        {
            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
        }).AddPolicyHandler(request => timeout);
CREUSER
la source
Si j'utilise cette structure, comment puis-je récupérer le contenu de ma réponse depuis httpClient? Je suis super nouveau dans c # et je ne pense pas que je comprends.
FoxDeploy
1
@FoxDeploy, aucune modification n'est nécessaire pour que le code obtienne le contenu lorsque vous utilisez cette solution. Voir ici pour référence: stackoverflow.com/questions/26597665/…
DIG
1
même s'il s'agit d'un ancien post, cette réponse vient de résoudre mon problème en .netcore, passant de 1.1 à 2.0, il semble que le client faisait automatiquement la décompression, j'ai donc dû ajouter ce code en 2.0 pour que cela fonctionne ... Merci !
Sebastian Castaldi
3
Juste pour se greffer sur @SebastianCastaldi, mais .net core 1.1 avait AutomaticDecompression correctement défini, mais dans .net core 2.0, il est défini sur AUCUN. Cela m'a pris beaucoup trop de temps pour comprendre ...
KallDrexx
5
Remarque: HttpClientNe doit PAS être utilisé à l'intérieurusing
imba-tjd
1

Ok donc j'ai finalement résolu mon problème. S'il y a de meilleures façons, veuillez me le faire savoir :-)

        public DataSet getData(string strFoo)
    {
        string url = "foo";

        HttpClient client = new HttpClient();
        HttpResponseMessage response;   
        DataSet dsTable = new DataSet();
        try
        {
               //Gets the headers that should be sent with each request
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
              //Returned JSON
            response = client.GetAsync(url).Result;
              //converts JSON to string
            string responseJSONContent = response.Content.ReadAsStringAsync().Result;
              //deserializes string to list
            var jsonList = DeSerializeJsonString(responseJSONContent);
              //converts list to dataset. Bad name I know.
            dsTable = Foo_ConnectAPI.ExtentsionHelpers.ToDataSet<RootObject>(jsonList);
              //Returns the dataset                
            return dsTable;
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show(ex.Message);
            return null;
        }
    }

       //deserializes the string to a list. Utilizes JSON.net. RootObject is a class that contains the get and set for the JSON elements

    public List<RootObject> DeSerializeJsonString(string jsonString)
    {
          //Initialized the List
        List<RootObject> list = new List<RootObject>();
          //json.net deserializes string
        list = (List<RootObject>)JsonConvert.DeserializeObject<List<RootObject>>(jsonString);

        return list;
    }

Le RootObject contient l'ensemble d'obtention qui obtiendra les valeurs du JSON.

public class RootObject
{  
      //These string will be set to the elements within the JSON. Each one is directly mapped to the JSON elements.
      //This only takes into account a JSON that doesn't contain nested arrays
    public string EntityID { get; set; }

    public string Address1 { get; set; }

    public string Address2 { get; set; }

    public string Address3 { get; set; }

}

Le moyen le plus simple de créer la ou les classes ci-dessus est d'utiliser json2charp qui le formatera en conséquence et fournira également les types de données corrects.

Ce qui suit provient d'une autre réponse sur Stackoverflow à nouveau, il ne prend pas en compte le JSON imbriqué.

    internal static class ExtentsionHelpers
{
    public static DataSet ToDataSet<T>(this List<RootObject> list)
    {
        try
        {
            Type elementType = typeof(RootObject);
            DataSet ds = new DataSet();
            DataTable t = new DataTable();
            ds.Tables.Add(t);

            try
            {
                //add a column to table for each public property on T
                foreach (var propInfo in elementType.GetProperties())
                {
                    try
                    {
                        Type ColType = Nullable.GetUnderlyingType(propInfo.PropertyType) ?? propInfo.PropertyType;

                            t.Columns.Add(propInfo.Name, ColType);

                    }
                    catch (Exception ex)
                    {
                        System.Windows.Forms.MessageBox.Show(ex.Message);
                    }

                }
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message);
            }

            try
            {
                //go through each property on T and add each value to the table
                foreach (RootObject item in list)
                {
                    DataRow row = t.NewRow();

                    foreach (var propInfo in elementType.GetProperties())
                    {
                        row[propInfo.Name] = propInfo.GetValue(item, null) ?? DBNull.Value;
                    }

                    t.Rows.Add(row);
                }
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message);
            }

            insert.insertCategories(t);
            return ds.
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show(ex.Message);

            return null;
        }
    }
};

Puis enfin pour insérer l'ensemble de données ci-dessus dans une table avec des colonnes qui ont été mappées au JSON, j'ai utilisé la copie en bloc SQL et la classe suivante

public class insert
{ 
    public static string insertCategories(DataTable table)
    {     
        SqlConnection objConnection = new SqlConnection();
          //As specified in the App.config/web.config file
        objConnection.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["foo"].ToString();

        try
        {                                 
            objConnection.Open();
            var bulkCopy = new SqlBulkCopy(objConnection.ConnectionString);

            bulkCopy.DestinationTableName = "dbo.foo";
            bulkCopy.BulkCopyTimeout = 600;
            bulkCopy.WriteToServer(table);

            return "";
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show(ex.Message);
            return "";
        }
        finally
        {
            objConnection.Close();
        }         
    }
};

Donc, ce qui précède fonctionne pour insérer JSON d'une webAPI dans une base de données. C'est quelque chose que je me mets au travail. Mais je ne m'attends en aucun cas à ce qu'il soit parfait. Si vous avez des améliorations, veuillez les mettre à jour en conséquence.

Corey
la source
2
Vous devez créer votre HttpClientet votre HttpResponseintérieur une using()déclaration chacun pour assurer l'élimination et la fermeture appropriées et en temps opportun des flux sous-jacents.
Ian Mercer
1

J'ai utilisé le code du lien ci-dessous pour décompresser le flux GZip, puis j'ai utilisé le tableau d'octets décompressé pour obtenir l'objet JSON requis. J'espère que cela peut aider quelqu'un.

var readTask = result.Content.ReadAsByteArrayAsync().Result;
var decompressedData = Decompress(readTask);
string jsonString = System.Text.Encoding.UTF8.GetString(decompressedData, 0, decompressedData.Length);
ResponseObjectClass responseObject = Newtonsoft.Json.JsonConvert.DeserializeObject<ResponseObjectClass>(jsonString);

https://www.dotnetperls.com/decompress

static byte[] Decompress(byte[] gzip)
{
    using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
    {
        const int size = 4096;
        byte[] buffer = new byte[size];
        using (MemoryStream memory = new MemoryStream())
        {
            int count = 0;
            do
            {
                count = stream.Read(buffer, 0, size);
                if (count > 0)
                {
                    memory.Write(buffer, 0, count);
                }
            }
            while (count > 0);
            return memory.ToArray();
        }
    }
}
NidhinSPradeep
la source