EonaCat.HttpClient 1.3.2

The ID prefix of this package has been reserved for one of the owners of this package by NuGet.org. Prefix Reserved
dotnet add package EonaCat.HttpClient --version 1.3.2
NuGet\Install-Package EonaCat.HttpClient -Version 1.3.2
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EonaCat.HttpClient" Version="1.3.2" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add EonaCat.HttpClient --version 1.3.2
#r "nuget: EonaCat.HttpClient, 1.3.2"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install EonaCat.HttpClient as a Cake Addin
#addin nuget:?package=EonaCat.HttpClient&version=1.3.2

// Install EonaCat.HttpClient as a Cake Tool
#tool nuget:?package=EonaCat.HttpClient&version=1.3.2

EonaCat HttpClient

This library can be used with the following frameworks:

  • .Net Framework 4.5+
  • .NET 5.0+
  • Xamarin iOS,
  • Xamarin Android
  • Xamarin Forms,
  • MAUI
  • .Net Core
  • UWP
  • Windows Phone

Features

  • Support of verbs : GET, POST , PUT, DELETE, PATCH and custom http verbs
  • Support of ETag (Entity file output)
  • Support of multi-part form data
  • Support of cancellation token on each request
  • Support of : download file and Upload file
  • Automatic XML and JSON serialization / deserialization
  • Support for custom serialisation / deserialisation
  • Support for compression and decompression (gzip and deflate)
  • Define timeout globally or per request
  • Timeout exception thrown if the request is in timeout
  • Provide an easy way to log : all requests send, all requests failed and the time it took to get a response.
  • Support of Basic Authentification
  • Support of OAuth2 Authentification
  • Auto retry mechanism (default 3 retries)
  • OpenApi Generator Support (EonaCat.HttpClient.OpenApi.Generator)

Basic usage

Create the client

using EonaCat.HttpClient;

var client = new EonaCat.HttpClient ("https://EonaCat.com/exampleApi"); 

or use:  

new EonaCat.HttpClient (new HttpClient(), "https://EonaCat.com/exampleApi");

Headers

Add a default header for all requests
// Add default header for each call
client.Settings.DefaultHeaders.Add("MyHeader", "Header");
// Add Auth2.0 token
client.Settings.DefaultHeaders.AddBearer("token");
// Add default basic authentication header
client.Settings.DefaultHeaders.AddBasicAuthentication("username", "password");
Add header for current request only
// Add header for this request only
client.GetRequest("Unicorn/All").
      AddHeader("MyHeader", "Header").
      ExecuteAsync();
// Add header for this request only
client.GetRequest("Unicorn/All").
      WithOAuthBearer("MYTOKEN").
      ExecuteAsync();
// Add basic authentication for this request only
client.GetRequest("Unicorn/All").
      WithBasicAuthentication("username", "password").
      ExecuteAsync();
Calculate headers before sending the request

Before the request is send to the server we can add / calculate headers to the request like below :

client.Settings.CalculateHeadersHandler = async () =>
{
   var token = await GetACustomTokenAsync();

   var headers = new Headers
   {
       { "CustomToken", token },
   };
   return headers;
};
Read headers of response
await client.GetRequest("Unicorn/GetAll").
             FillResponseHeaders(out headersOfResponse Headers).
             ExecuteAsync();
foreach(var header in headersOfResponse)
{
    foreach (var item in current.Value)
    {
        Debug.WriteLine(item);
    }
}

Basic GET http requests

var unicorns = client.GetRequest("Unicorn/All").ExecuteAsync<List<Unicorn>>();
// GET https://EonaCat.com/exampleApi/Unicorn/All

// Add a query parameter
var cities = client.
    GetRequest("Unicorn").
    AddQueryParameter("id", 2).
    AddQueryParameter("color", "Pink").
    ExecuteAsync<Unicorn>> ();

// GET https://EonaCat.com/exampleApi/Unicorn?id=2&color=Pink 

Basic POST http requests

// POST
 var unicorn = new Unicorn() { Name = "Trixy" , Color = "Pink"};

// With content
var response = await client.PostRequest("Unicorn", unicorn).
                ExecuteAsync<bool>();
// POST https://EonaCat.com/exampleApi/Unicorn

// With form url encoded data
var response = await client.
                PostRequest("Unicorn/Add").
                AddFormParameter("color", "Pink").
                AddFormParameter("name", "Trixy").
                ExecuteAsync<Response>();
// POST https://EonaCat.com/exampleApi/Unicorn/Add


var fileInfo = new FileInfo("myTextFile.txt");
var response = await client.
                PostRequest("Unicorn/Image/Add").
                AddFileContent(fileInfo, "text/plain").
                ExecuteAsync<Response>();
// POST text file at https://EonaCat.com/exampleApi/Unicorn/Add 

Custom Http Verb requests

 await client.
       NewRequest(new System.Net.Http.HttpMethod("HEAD"), "Unicorn/All").
       ExecuteAsync<List<Unicorn>>();

Define timeout

Define a global timeout for the client. (By default 100 seconds)

client.Settings.DefaultTimeout = TimeSpan.FromSeconds(100);

Define the timeout for one request

request.WithTimeout(TimeSpan.FromSeconds(100));

Allow non http 2xx responses

Globaly

Allow any status codes :

client.Settings.HttpStatusCodeAllowed.AllowAnyStatus = true;

Allow only a range of http status codes :

client.Settings.HttpStatusCodeAllowed.Add(new HttpStatusRange(400, 420));

or

client.Settings.HttpStatusCodeAllowed.Add(new HttpStatusRange(System.Net.HttpStatusCode.BadRequest, System.Net.HttpStatusCode.BadGateway));
By request

Allow all status code :

request.AllowAllHttpStatusCode().ExecuteAsync();

Allow only a range of http status codes :

request.AllowRangeHttpStatusCode(400, 420).ExecuteAsync();

Allow only on stats code of http status codes :

request.AllowSpecificHttpStatusCode(409).ExecuteAsync();

Download file

string filePath = "c:\map.pdf";
FileInfo fileInfo = await client.
                GetRequest("Unicorn/map.pdf").
                DownloadFileAsync("c:\map.pdf");
// GET https://EonaCat.com/exampleApi/Unicorn/map.pdf 

Get raw HttpResponseMessage

var response = await client.
                PostRequest("Unicorn/Add").
                AddFormParameter("color", "Pink").
                AddFormParameter("name", "CoolUnicorn").
                ExecuteAsHttpResponseMessageAsync();
// POST https://EonaCat.com/exampleApi/Unicorn/Add with from url encoded content

Get raw string result

string response = await client.
                GetRequest("Unicorn/All").
                ExecuteAsStringAsync();
// GET https://EonaCat.com/exampleApi/Unicorn/All with from url encoded content

Multi-part form data

// With 2 json content
var Unicorn1 = new Unicorn() { Name = "CoolUnicorn" , Color = "Pink"};
var Unicorn2 = new Unicorn() { Name = "CrappyUnicorn" , Color = "Blue"};
var response = await client.NewRequest(HttpVerb.Post, "Unicorn").
await client.PostRequest("MultiPart/Test").
              AsMultiPartFromDataRequest().
              AddContent<Unicorn>(Unicorn1, "Unicorn1", "Unicorn1.json").
              AddContent<Unicorn>(Unicorn2, "Unicorn2", "Unicorn2.json").
              ExecuteAsync();


// With 2 byte array content
byte[] byteArray1 = ...
byte[] byteArray2 = ...           
              
await client.PostRequest("MultiPart/Test").
              AsMultiPartFromDataRequest().
              AddByteArray(byteArray1, "request", "request2.bin").
              AddByteArray(byteArray2, "request", "request2.bin")
              ExecuteAsync();
  

// With 2 streams content        
Stream1 stream1 = ...
Stream stream2 = ...         
await client.PostRequest("MultiPart/Test").
              AsMultiPartFromDataRequest().
              AddStream(stream1, "request", "request2.bin").
              AddStream(stream2, "request", "request2.bin")
              ExecuteAsync();
              
              
// With 2 files content           

var fileInfo1 = new FileInfo("myTextFile1.txt");
var fileInfo2 = new FileInfo("myTextFile2.txt");

var response = await client.
                PostRequest("Unicorn/Image/Add").
                AsMultiPartFromDataRequest().
                AddFileContent(fileInfo1, "text/plain").
                AddFileContent(fileInfo2, "text/plain").
                ExecuteAsync<Response>();
                
                
// With 2 strings content   
var response = await client.
                PostRequest("Unicorn/Image/Text").
                AsMultiPartFromDataRequest().
                AddString("string1", "text/plain").
                AddString("string2", "text/plain").
                ExecuteAsync<Response>();

// With mixed content                  
await client.PostRequest("Files/Add").
              AsMultiPartFromDataRequest().
              AddContent<Unicorn>(Unicorn1, "Unicorn1", "Unicorn1.json").
              AddByteArray(byteArray1, "request", "request2.bin").
              AddStream(stream2, "request", "request2.bin")
              AddString("string1", "text", "request.txt")
              ExecuteAsync();

String, Streams and bytes array

With the String, Stream or bytes array no serializer will be used.

String

// Read string response
 Stream stream = await client.
              GetRequest("text").
              ExecuteAsStringAsync();
              
// Post string as content
await client.PostRequest("plain/text").
            AddStringContent(stream).
            ExecuteAsync();

Streams

// Read stream response
 Stream stream = await client.
              GetRequest("File").
              ExecuteAsStreamAsync();

// Post stream as content
await client.PostRequest("File/Add").
            AddStreamContent(stream).
            ExecuteAsync();

Byte array

// Read byte array response         
byte[] byteArray = await client.
              GetRequest("File").
              ExecuteAsByteArrayAsync();

// Read byte array as content
await client.
            PostRequest("File/Add").
            AddByteArrayContent(byteArray).
            ExecuteAsync();

Error handling

All requests can throw 5 exceptions :

  • ConnectionException : thrown when the request can't reach the server
  • HttpException : thrown when the server has invalid error code
  • SerializeException : thrown when the serializer can't serialize the content
  • DeserializeException : thrown when the deserializer can't deserialize the response
  • TimeoutException : thrown when the request take too much time to be executed

Catch a specific error code

string UnicornName = "Trixy";
try
{ 
   var response = await client.
     GetRequest("Unicorn").
     AddQueryParameter("Name", UnicornName).
     ExecuteAsync<Unicorn>();
}
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
   throw new UnicornNotFoundException(UnicornName);
}
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{
   throw new ServerErrorException($"{ex.Message} {ex.ReasonPhrase}");
}

Encapsulate HttpExceptions

We can setup a global handler to provide logic to encapsulate HttpException automatically.

For example I can choose to translate all HttpException with StatusCode NotFound in a NotFoundCustomException.

client.Settings.EncapsulateHttpExceptionHandler = (ex) =>
{
   if (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
   {
      return new NotFoundCustomException();
   }

   return ex;
};

Now if I call an API which responds with status code NotFound - it will automaticaly throw the custom exception.

// Call an API which throw NotFound error
await client.GetRequest("APIWhichDoesNotExist").ExecuteAsync();

ETag

Notice: although support is added for Entity tags, it is disabled by default.

Define an ETagContainer globally

An implementation of IETagContainer is provided. It stores all data in multiples files.

To enable it :

client.Settings.ETagContainer = new ETagFileContainer(@"C:\ETagFolder");

Define an ETagContainer for one request

You can also define the ETagContainer only on specific request.

request.WithETagContainer(eTagContainer);

Formatters

By default :

  • the Json is used as default Formatter.
  • Xml Formatter is added in Formatters

Each formatter has a list of supported media types. If no formatter is found it uses the default formatter.

Add a new formatter

Add a new custom formatter as default formatter.

bool isDefaultFormatter = true;
var customFormatter = new CustomFormatter();
client.Settings.Formatters.Add(customFormatter, isDefaultFormatter);

Remove a formatter

var lastFormatter = client.Settings.Formatters.First(f => f is XmlSerializer);
client.Settings.Formatters.Remove(lastFormatter);

Define a specific serializer for one request

IFormatter serializer = new XmlFormatter();
 var response = await client.
     PostRequest("Unicorn", Unicorn, serializer).
     ExecuteAsync();

Define a specific deserializer for one request

IFormatter deserializer = new XmlFormatter();

 var response = await client.
     GetRequest("Unicorn").
     AddQueryParameter("Name", UnicornName).
     ExecuteAsync<Unicorn>(deserializer);

Custom formatter

You can create your own serializer/deserializer by implementing IFormatter

For example the implementation of XmlFormatter:

public class XmlFormatter : IFormatter
{

   public string DefaultMediaType { get; set; } = "application/xml";

   public IEnumerable<string> SupportedMediaTypes
   {
      get
      {
         yield return "application/xml";
         yield return "text/xml";
      }
   }

   public T Deserialize<T>(Stream stream, Encoding encoding)
   {
      using (var reader = new StreamReader(stream, encoding))
      {
         var serializer = new XmlSerializer(typeof(T));
         return (T)serializer.Deserialize(reader);
      }
   }

   public string Serialize<T>(T data, Encoding encoding)
   {
         if (data == default)
         {
             return null;
         }

         var serializer = new XmlSerializer(data.GetType());
         using (var stringWriter = new DynamicEncodingStringWriter(encoding))
         {
            serializer.Serialize(stringWriter, data);
            return stringWriter.ToString();
         }
      }
   }

Listeners

You can easily add a listener to listen to all the sent requests / responses received + exceptions.

Debug listener : which logs all requests in debug console

Debug Listener

To add a Debug listener you have to call AddDebug on Listeners property


client.Settings.Listeners.AddDebug();

It produce this type of output in debug window for each ExecuteAsync called :

curl -X POST "https://localhost:1337/api/test/hello"-H "Accept: application/json" -H "Content-Type: application/json" -d "{\"Id\":42,\"Data\":\"DATA\"}"

If you only want the Json of a collection you can call the method GetCollectionJson

listener.GetCollectionJson();

Custom Listener

You can also create you own listener by implementing IListener.

IListener myCustomListerner = ..
client.Settings.Listeners.Add(myCustomListerner);

Compression and Decompression

By default, the client supports the decompression of Gzip and deflate.

If the server respond with the header ContentEncoding "gzip" or "deflate" the client will decompress it automaticly.

Compression

For each request which posts a content you can specified the compression algorithm like below

var response = await client.
                PostRequest("Gzip/complex", postRequest, compression: client.Settings.Compressions["gzip"]).
                ExecuteAsync<Response>();

Warning : the server must be able to decompress your content.

Decompression

Even if it's supported the client didn't send Accept-Encoding header automaticaly.

You can add it for gzip for all requests like below :

var compression = client.Settings.Compressions["gzip"];
compression.AddAcceptEncodingHeader = true;

You can add it for deflate for all requests like below :

var compression = client.Settings.Compressions["deflate"];
compression.AddAcceptEncodingHeader = true;

If the server compresses the response, it will respond with the compressed content.

Custom ICompression

You can add your own compression / decompression algorithm :

client.Settings.Add(new CustomCompression());

Your class must implement the interface ICompression.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.3.2 177 7/24/2023