Softlion.FluentRest 1.1.0-ci14248954729

Prefix Reserved
This is a prerelease version of Softlion.FluentRest.
dotnet add package Softlion.FluentRest --version 1.1.0-ci14248954729
                    
NuGet\Install-Package Softlion.FluentRest -Version 1.1.0-ci14248954729
                    
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="Softlion.FluentRest" Version="1.1.0-ci14248954729" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Softlion.FluentRest" Version="1.1.0-ci14248954729" />
                    
Directory.Packages.props
<PackageReference Include="Softlion.FluentRest" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Softlion.FluentRest --version 1.1.0-ci14248954729
                    
#r "nuget: Softlion.FluentRest, 1.1.0-ci14248954729"
                    
#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.
#:package Softlion.FluentRest@1.1.0-ci14248954729
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Softlion.FluentRest&version=1.1.0-ci14248954729&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Softlion.FluentRest&version=1.1.0-ci14248954729&prerelease
                    
Install as a Cake Tool

FluentRest

NuGet

A small, simple and powerful .net6, maui compatible, and System.Text.Json only HTTP client library (also compatible with xamarin and .net core).
Based on the amazing work from Todd Menier.
It quickly replaces heavy/old libs like RestSharp.

var result = await "https://api.mysite.com"
    .AppendPathSegment("person")
    .SetQueryParams(new { api_key = "xyz" })
    .WithOAuthBearerToken("my_oauth_token")
    .PostJsonAsync(new { first_name = firstName, last_name = lastName })
    .ReceiveJson<T>();

When using xxxJson methods, the mapping into a C# object is done by System.Text.Json.

Business Use Cases

Common code:

private const string Endpoint = "https://my.api.com/";

Use case insensitive JSON mapping globally

FluentRestHttp.Configure(settings =>
{
    settings.JsonSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions
    {
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
        PropertyNameCaseInsensitive = true
    });
});

Post to an API and get the json result

var response = await Endpoint.AppendPathSegment("signin").AllowAnyHttpStatus()
                             .PostJsonAsync(new { login = email });
if (response.StatusCode is not (>=200 and <300) && response.StatusCode != (int)HttpStatusCode.Conflict)
    return null;
var data = await response.GetJsonAsync<ApiSigninResponse>();
...

Get an API with an optional parameter

var query = Endpoint.AppendPathSegment("1.0/account/find")
                .AllowAnyHttpStatus()
                .SetQueryParam("someParameter", text1)
                .SetQueryParam("otherParameter", text2)
                .WithOAuthBearerToken(token);

if (userLocation != null)
{
    query.SetQueryParam("latitude", userLocation.Latitude.ToString(CultureInfo.InvariantCulture));
}

var response = await query.GetAsync();

if (response.StatusCode is not (>= 200 and < 300))
    return null;

return await response.GetJsonAsync<List<ApiSomeResult>>();

Extract a Bearer token from the response header

var response = await Endpoint.AppendPathSegment("signin").AllowAnyHttpStatus()
                             .PostJsonAsync(new { username = email, password });
if (response.StatusCode is not (>= 200 and < 300))
    return false;
if (!response.Headers.TryGetFirst("Authorization", out var authorization)
    || authorization?.StartsWith("Bearer ") != true)
    return false;
var sessionToken = authorization.Split(" ")[1];

Add data in the header of each request globally

FluentRestHttp.Configure(settings =>
{
    //Mobile xamarin app
    var platform = Xamarin.Essentials.DeviceInfo.Platform.ToString();
    var version = Xamarin.Essentials.DeviceInfo.Version.ToString();
    var build = Xamarin.Essentials.AppInfo.BuildString;

    settings.BeforeCall = call =>
    {
        call.Request.Headers.Add("x-app-platform", platform);
        call.Request.Headers.Add("x-app-platform-version", version);
        call.Request.Headers.Add("x-app-version", build);
    };
});

Prevent throwing an exception if the HTTP call fails (when internet is offline)

FluentRestHttp.Configure(settings =>
{
    settings.OnError = call =>
    {
        //If the call fails with an exception, return notfound instead of throwing
        if (call.Exception != null)
        {
            call.Response = new FluentRestResponse(new HttpResponseMessage(HttpStatusCode.NotFound));
            call.ExceptionHandled = true;
        }
    };
}

Refresh a JWT automatically

This snippet checks the JWT for expiration, and refreshes it before any api call.
The check happens only for api calls having an Authorization header, so obviously requiring a valid JWT.
ApiSignIn() must not have an Authorization header as this would create an infinite loop.

//In a class
private readonly SemaphoreSlim sync = new (1,1);
public string? AuthorizationToken { get; private set; }

//In the class constructor
settings.BeforeCallAsync = async call =>
{
    if (call.Request.Headers.Contains("Authorization"))
    {
        //Make sure the token is still valid. If we can't validate it, disconnect and go back to login screen.
        if (AuthorizationToken != null)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var validationParameters = new TokenValidationParameters
            {
                ValidateLifetime = true,
                ValidateAudience = false,
                ValidateIssuer = false,
                ValidateActor = false,
                ValidateTokenReplay = false,
                ValidateIssuerSigningKey = false,
                //IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) // The same key as the one that generated the token
            };

            try
            {
                tokenHandler.ValidateToken(AuthorizationToken, validationParameters, out var _);
            }
            catch (Exception)
            {
                //Invalid JWT: try relogin once
                var old = AuthorizationToken;
                //sync required as this can be called by multiple threads simultaneously; and we want to refresh only once.
                await sync.WaitAsync();

                try
                {
                    if (old != AuthorizationToken)
                    {
                        if(AuthorizationToken != null)
                            call.Request.WithOAuthBearerToken(AuthorizationToken);
                    }
                    else if (userEmail != null && userPassword != null)
                    {
                        //we use the last auth info stored locally. You have to provide pour own login code, as this vary from service to service.
                        AuthorizationToken = await ApiSignIn(userEmail, userPassword);
                        if (AuthorizationToken != null)
                            call.Request.WithOAuthBearerToken(AuthorizationToken);
                    }
                    else
                        AuthorizationToken = null;
                        
                    if (AuthorizationToken == null)
                        await Logout();
                }
                finally
                {
                    sync.Release();
                }
            }
        }
    }
};


//Example use
const string Endpoint = "https://your.api.endpoint";
public async Task<ApiCallResultModel?> Hotspot_Status(CancellationToken cancel)
{
    var response = await Endpoint.AppendPathSegment("/some/api").AllowAnyHttpStatus()
        .WithOAuthBearerToken(AuthorizationToken)
        .GetAsync(cancel);

    if (response.StatusCode is not (>= 200 and < 300))
        return null;

    return await response.GetJsonAsync<ApiCallResultModel>();
}

Disable https certificate validation

public class UntrustedCertClientFactory : DefaultHttpClientFactory
{
    public override HttpMessageHandler CreateMessageHandler() 
      => new HttpClientHandler { ServerCertificateCustomValidationCallback = (_, _, _, _) => true }; 
}

FluentRestHttp.ConfigureClient(Endpoint, client => client.Settings.HttpClientFactory = new UntrustedCertClientFactory());

Delete an item

public async Task<bool> Remove(string itemId)
{
    var response = await Endpoint.AppendPathSegment($"remove/{itemId}/").AllowAnyHttpStatus()
        .WithOAuthBearerToken(userInfo!.Authorization)
        .DeleteAsync();

    return response.StatusCode is >= 200 and < 300;
}

Handling errors

When an http or json error occurs, the global custom handlers OnError and OnErrorAsync are both called in this order respectively.

If you set ExceptionHandled to true in the object received by one of these handlers, the exception is ignored. Then for http errors, you should set the Response property and it will be returned to the original caller. For json parsing errors, default(T) is always returned, you can not change this value.

If you don't set ExceptionHandled to true, the original call will throw one of the exception below.

  • FluentRestParsingException when json parsing fails (for json methods like GetJsonAsync<T>)
  • FluentRestHttpTimeoutException when a timeout occurs
  • FluentRestHttpException when a http call fails directly (ie: domain not found, connection failed, ...)
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net9.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Softlion.FluentRest:

Package Downloads
Softlion.NotionSharp

Notion.so is a nice tool to write blogs among other things

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0-ci14248954729 183 4/3/2025
1.0.7 449 1/13/2024
1.0.6 301 11/7/2023
1.0.5 279 5/19/2023
1.0.4 575 11/27/2022
1.0.3 556 6/12/2022
1.0.2 735 2/13/2022
1.0.1 584 1/17/2022
1.0.0 377 1/12/2022

1.1.0: .NET 9 support and package updates
           1.0.8: NuGet dependency updates
           1.0.7: NuGet dependency updates
           1.0.6: .NET 8 support
           1.0.3: .NET 7 support
           1.0.2: Add automatic refresh of bearer token
           1.0.1: Set JSON defaults as ignore property case and ignore write default
           1.0.0: Initial release