EonaCat Http Client for REST JSON and XML messages.
$ dotnet add package EonaCat.HttpClientThis library can be used with the following frameworks:
using EonaCat.HttpClient;
var client = new EonaCat.HttpClient ("https://EonaCat.com/exampleApi");
or use:
new EonaCat.HttpClient (new HttpClient(), "https://EonaCat.com/exampleApi");
// 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 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();
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;
};
await client.GetRequest("Unicorn/GetAll").
FillResponseHeaders(out headersOfResponse Headers).
ExecuteAsync();
foreach(var header in headersOfResponse)
{
foreach (var item in current.Value)
{
Debug.WriteLine(item);
}
}
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
// 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
await client.
NewRequest(new System.Net.Http.HttpMethod("HEAD"), "Unicorn/All").
ExecuteAsync<List<Unicorn>>();
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 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));
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();
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
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
string response = await client.
GetRequest("Unicorn/All").
ExecuteAsStringAsync();
// GET https://EonaCat.com/exampleApi/Unicorn/All with from url encoded content
// 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();
With the String, Stream or bytes array no serializer will be used.
// Read string response
Stream stream = await client.
GetRequest("text").
ExecuteAsStringAsync();
// Post string as content
await client.PostRequest("plain/text").
AddStringContent(stream).
ExecuteAsync();
// Read stream response
Stream stream = await client.
GetRequest("File").
ExecuteAsStreamAsync();
// Post stream as content
await client.PostRequest("File/Add").
AddStreamContent(stream).
ExecuteAsync();
// Read byte array response
byte[] byteArray = await client.
GetRequest("File").
ExecuteAsByteArrayAsync();
// Read byte array as content
await client.
PostRequest("File/Add").
AddByteArrayContent(byteArray).
ExecuteAsync();
All requests can throw 5 exceptions :
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}");
}
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();
Notice: although support is added for Entity tags, it is disabled by default.
An implementation of IETagContainer is provided. It stores all data in multiples files.
To enable it :
client.Settings.ETagContainer = new ETagFileContainer(@"C:\ETagFolder");
You can also define the ETagContainer only on specific request.
request.WithETagContainer(eTagContainer);
By default :
Each formatter has a list of supported media types. If no formatter is found it uses the default formatter.
Add a new custom formatter as default formatter.
bool isDefaultFormatter = true;
var customFormatter = new CustomFormatter();
client.Settings.Formatters.Add(customFormatter, isDefaultFormatter);
var lastFormatter = client.Settings.Formatters.First(f => f is XmlSerializer);
client.Settings.Formatters.Remove(lastFormatter);
IFormatter serializer = new XmlFormatter();
var response = await client.
PostRequest("Unicorn", Unicorn, serializer).
ExecuteAsync();
IFormatter deserializer = new XmlFormatter();
var response = await client.
GetRequest("Unicorn").
AddQueryParameter("Name", UnicornName).
ExecuteAsync<Unicorn>(deserializer);
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();
}
}
}
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
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();
You can also create you own listener by implementing IListener.
IListener myCustomListerner = ..
client.Settings.Listeners.Add(myCustomListerner);
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.
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.
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.
You can add your own compression / decompression algorithm :
client.Settings.Add(new CustomCompression());
Your class must implement the interface ICompression.