{"id":20997355,"url":"https://github.com/softlion/fluentrest","last_synced_at":"2025-10-20T01:24:01.451Z","repository":{"id":65460735,"uuid":"447363303","full_name":"softlion/FluentRest","owner":"softlion","description":"Easy, simple, powerful, and understandable http requests from this http client library","archived":false,"fork":false,"pushed_at":"2025-04-03T17:17:55.000Z","size":272,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-09T17:20:00.153Z","etag":null,"topics":["dotnet","http-client","http-requests","maui","xamarin"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/softlion.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-12T20:35:51.000Z","updated_at":"2025-04-13T05:33:44.000Z","dependencies_parsed_at":"2024-01-13T10:21:34.437Z","dependency_job_id":"0be4a513-8817-40db-8359-1ede08b41f0b","html_url":"https://github.com/softlion/FluentRest","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softlion%2FFluentRest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softlion%2FFluentRest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softlion%2FFluentRest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softlion%2FFluentRest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/softlion","download_url":"https://codeload.github.com/softlion/FluentRest/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254247964,"owners_count":22038932,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet","http-client","http-requests","maui","xamarin"],"created_at":"2024-11-19T07:39:36.945Z","updated_at":"2025-10-20T01:24:01.334Z","avatar_url":"https://github.com/softlion.png","language":"C#","readme":"# FluentRest\n\n\n[![NuGet](https://img.shields.io/nuget/dt/Softlion.FluentRest?label=Get%20It%20On%20Nuget\u0026style=for-the-badge)](https://www.nuget.org/packages/Softlion.FluentRest/)  \n\n\nA small, simple and powerful .net6, maui compatible, and System.Text.Json only HTTP client library (also compatible with xamarin and .net core).  \nBased on the amazing work from Todd Menier.  \nIt quickly replaces heavy/old libs like RestSharp.\n\n```c#\nvar result = await \"https://api.mysite.com\"\n    .AppendPathSegment(\"person\")\n    .SetQueryParams(new { api_key = \"xyz\" })\n    .WithOAuthBearerToken(\"my_oauth_token\")\n    .PostJsonAsync(new { first_name = firstName, last_name = lastName })\n    .ReceiveJson\u003cT\u003e();\n```\n\nWhen using xxxJson methods, the mapping into a C# object is done by `System.Text.Json`.\n\n## Business Use Cases\n\nCommon code:\n```c#\nprivate const string Endpoint = \"https://my.api.com/\";\n```\n\n### Use case insensitive JSON mapping globally\n\n```c#\nFluentRestHttp.Configure(settings =\u003e\n{\n    settings.JsonSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions\n    {\n        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,\n        PropertyNameCaseInsensitive = true\n    });\n});\n```\n\n### Post to an API and get the json result\n\n```C#\nvar response = await Endpoint.AppendPathSegment(\"signin\").AllowAnyHttpStatus()\n                             .PostJsonAsync(new { login = email });\nif (response.StatusCode is not (\u003e=200 and \u003c300) \u0026\u0026 response.StatusCode != (int)HttpStatusCode.Conflict)\n    return null;\nvar data = await response.GetJsonAsync\u003cApiSigninResponse\u003e();\n...\n```\n\n### Get an API with an optional parameter\n```c#\nvar query = Endpoint.AppendPathSegment(\"1.0/account/find\")\n                .AllowAnyHttpStatus()\n                .SetQueryParam(\"someParameter\", text1)\n                .SetQueryParam(\"otherParameter\", text2)\n                .WithOAuthBearerToken(token);\n\nif (userLocation != null)\n{\n    query.SetQueryParam(\"latitude\", userLocation.Latitude.ToString(CultureInfo.InvariantCulture));\n}\n\nvar response = await query.GetAsync();\n\nif (response.StatusCode is not (\u003e= 200 and \u003c 300))\n    return null;\n\nreturn await response.GetJsonAsync\u003cList\u003cApiSomeResult\u003e\u003e();\n```\n\n### Extract a Bearer token from the response header\n```c#\nvar response = await Endpoint.AppendPathSegment(\"signin\").AllowAnyHttpStatus()\n                             .PostJsonAsync(new { username = email, password });\nif (response.StatusCode is not (\u003e= 200 and \u003c 300))\n    return false;\nif (!response.Headers.TryGetFirst(\"Authorization\", out var authorization)\n    || authorization?.StartsWith(\"Bearer \") != true)\n    return false;\nvar sessionToken = authorization.Split(\" \")[1];\n```\n\n### Add data in the header of each request globally\n```c#\nFluentRestHttp.Configure(settings =\u003e\n{\n    //Mobile xamarin app\n    var platform = Xamarin.Essentials.DeviceInfo.Platform.ToString();\n    var version = Xamarin.Essentials.DeviceInfo.Version.ToString();\n    var build = Xamarin.Essentials.AppInfo.BuildString;\n\n    settings.BeforeCall = call =\u003e\n    {\n        call.Request.Headers.Add(\"x-app-platform\", platform);\n        call.Request.Headers.Add(\"x-app-platform-version\", version);\n        call.Request.Headers.Add(\"x-app-version\", build);\n    };\n});\n```\n\n### Prevent throwing an exception if the HTTP call fails (when internet is offline)\n```c#\nFluentRestHttp.Configure(settings =\u003e\n{\n    settings.OnError = call =\u003e\n    {\n        //If the call fails with an exception, return notfound instead of throwing\n        if (call.Exception != null)\n        {\n            call.Response = new FluentRestResponse(new HttpResponseMessage(HttpStatusCode.NotFound));\n            call.ExceptionHandled = true;\n        }\n    };\n}\n```\n\n### Refresh a JWT automatically\nThis snippet checks the JWT for expiration, and refreshes it before any api call.  \nThe check happens only for api calls having an Authorization header, so obviously requiring a valid JWT.  \n`ApiSignIn()` must not have an Authorization header as this would create an infinite loop.\n\n```c#\n//In a class\nprivate readonly SemaphoreSlim sync = new (1,1);\npublic string? AuthorizationToken { get; private set; }\n\n//In the class constructor\nsettings.BeforeCallAsync = async call =\u003e\n{\n    if (call.Request.Headers.Contains(\"Authorization\"))\n    {\n        //Make sure the token is still valid. If we can't validate it, disconnect and go back to login screen.\n        if (AuthorizationToken != null)\n        {\n            var tokenHandler = new JwtSecurityTokenHandler();\n            var validationParameters = new TokenValidationParameters\n            {\n                ValidateLifetime = true,\n                ValidateAudience = false,\n                ValidateIssuer = false,\n                ValidateActor = false,\n                ValidateTokenReplay = false,\n                ValidateIssuerSigningKey = false,\n                //IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) // The same key as the one that generated the token\n            };\n\n            try\n            {\n                tokenHandler.ValidateToken(AuthorizationToken, validationParameters, out var _);\n            }\n            catch (Exception)\n            {\n                //Invalid JWT: try relogin once\n                var old = AuthorizationToken;\n                //sync required as this can be called by multiple threads simultaneously; and we want to refresh only once.\n                await sync.WaitAsync();\n\n                try\n                {\n                    if (old != AuthorizationToken)\n                    {\n                        if(AuthorizationToken != null)\n                            call.Request.WithOAuthBearerToken(AuthorizationToken);\n                    }\n                    else if (userEmail != null \u0026\u0026 userPassword != null)\n                    {\n                        //we use the last auth info stored locally. You have to provide pour own login code, as this vary from service to service.\n                        AuthorizationToken = await ApiSignIn(userEmail, userPassword);\n                        if (AuthorizationToken != null)\n                            call.Request.WithOAuthBearerToken(AuthorizationToken);\n                    }\n                    else\n                        AuthorizationToken = null;\n                        \n                    if (AuthorizationToken == null)\n                        await Logout();\n                }\n                finally\n                {\n                    sync.Release();\n                }\n            }\n        }\n    }\n};\n\n\n//Example use\nconst string Endpoint = \"https://your.api.endpoint\";\npublic async Task\u003cApiCallResultModel?\u003e Hotspot_Status(CancellationToken cancel)\n{\n    var response = await Endpoint.AppendPathSegment(\"/some/api\").AllowAnyHttpStatus()\n        .WithOAuthBearerToken(AuthorizationToken)\n        .GetAsync(cancel);\n\n    if (response.StatusCode is not (\u003e= 200 and \u003c 300))\n        return null;\n\n    return await response.GetJsonAsync\u003cApiCallResultModel\u003e();\n}\n```\n\n### Disable https certificate validation\n```c#\npublic class UntrustedCertClientFactory : DefaultHttpClientFactory\n{\n    public override HttpMessageHandler CreateMessageHandler() \n      =\u003e new HttpClientHandler { ServerCertificateCustomValidationCallback = (_, _, _, _) =\u003e true }; \n}\n\nFluentRestHttp.ConfigureClient(Endpoint, client =\u003e client.Settings.HttpClientFactory = new UntrustedCertClientFactory());\n```\n\n### Delete an item\n```c#\npublic async Task\u003cbool\u003e Remove(string itemId)\n{\n    var response = await Endpoint.AppendPathSegment($\"remove/{itemId}/\").AllowAnyHttpStatus()\n        .WithOAuthBearerToken(userInfo!.Authorization)\n        .DeleteAsync();\n\n    return response.StatusCode is \u003e= 200 and \u003c 300;\n}\n```\n\n## Handling errors\n\nWhen an http or json error occurs, the global custom handlers `OnError` and `OnErrorAsync` are both called in this order respectively.\n\nIf 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.\n\nIf you don't set `ExceptionHandled` to true, the original call will throw one of the exception below.\n\n* `FluentRestParsingException` when json parsing fails (for json methods like `GetJsonAsync\u003cT\u003e`)\n* `FluentRestHttpTimeoutException` when a timeout occurs\n* `FluentRestHttpException` when a http call fails directly (ie: domain not found, connection failed, ...)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftlion%2Ffluentrest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoftlion%2Ffluentrest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftlion%2Ffluentrest/lists"}