{"id":14975966,"url":"https://github.com/cajuncoding/flurlgraphql","last_synced_at":"2025-10-27T17:30:24.052Z","repository":{"id":65855244,"uuid":"593927027","full_name":"cajuncoding/FlurlGraphQL","owner":"cajuncoding","description":"Lightweight, simple, fluent GraphQL client API extensions for the amazing Flurl Http library!","archived":false,"fork":false,"pushed_at":"2024-06-04T03:11:43.000Z","size":981,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-07-21T14:33:24.249Z","etag":null,"topics":["flurl","flurl-extensions","graphql","graphql-api","graphql-api-client","graphql-client","graphql-dotnet","graphql-net","graphql-netcore","graphql-query","graphql-server","hotchocolate"],"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/cajuncoding.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2023-01-27T06:55:39.000Z","updated_at":"2024-08-02T07:53:17.981Z","dependencies_parsed_at":"2024-02-09T05:20:50.088Z","dependency_job_id":"4b1bb29b-a4b2-44c7-b443-7228bcdcbbcb","html_url":"https://github.com/cajuncoding/FlurlGraphQL","commit_stats":null,"previous_names":["cajuncoding/flurl.http.graphql"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cajuncoding%2FFlurlGraphQL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cajuncoding%2FFlurlGraphQL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cajuncoding%2FFlurlGraphQL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cajuncoding%2FFlurlGraphQL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cajuncoding","download_url":"https://codeload.github.com/cajuncoding/FlurlGraphQL/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219860932,"owners_count":16556009,"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":["flurl","flurl-extensions","graphql","graphql-api","graphql-api-client","graphql-client","graphql-dotnet","graphql-net","graphql-netcore","graphql-query","graphql-server","hotchocolate"],"created_at":"2024-09-24T13:52:57.109Z","updated_at":"2025-10-27T17:30:24.045Z","avatar_url":"https://github.com/cajuncoding.png","language":"C#","readme":"﻿# FlurlGraphQL\n`FlurlGraphQL` is a lightweight, simplified, asynchronous, fluent GraphQL client querying API extensions for the amazing Flurl Http library!\n\nThis makes it super easy to execute ad-hoc and simple or advanced queries against a GraphQL API such as the awesome [HotChocolate .NET GraphQL Server](https://chillicream.com/docs/hotchocolate/v13).\n\nOne of the primary goals of `FlurlGraphQL` is to help to prevent you from getting bogged down in details like what should the GraphQL payload look like, how to handle and parse errors, or \npolluting your data models with unnecessary properties like `Edges`, `Nodes`, `Items`, etc. for paginated data. Handling paginated queries and results is dramatically simplified, making it \neasy and intuitive to retreive any single page, all pages/results, and even stream the results via `IAsyncEnumerable` in `netstandard2.1` (or `IEnumerable\u003cTask\u003e` in `netstandard2.0`).\n\nThe spirit of Flurl is fully maintained in this api so you can start your query from your endpoint, fully configure the request just as you would with any Flurl request \n(e.g. manage Headers, Auth Tokens, Query params, Json Serialization settings, etc.).\n\nHowever, since GraphQL has unique elements we now provide ability to quickly set the query, GraphQL query variables (similar to how you would with Query Params), etc. and\nthen retrieve the results from the GraphQL Json response in any number of ways -- all of which are named descriptively and intuitivly follow the spirit of Flurl.\n\n#### Give us a Star ⭐\n*If you found FlurlGraphQL helpful, the easiest way for you to help is to give us a GitHub Star ⭐! It's completely free and will help the project out!*\n\n#### [Buy me a Coffee ☕](https://www.buymeacoffee.com/cajuncoding)\n*I'm happy to share with the community, but if you find this useful (e.g for professional use), and are so inclinded,\nthen I do love-me-some-coffee!*\n\n\u003ca href=\"https://www.buymeacoffee.com/cajuncoding\" target=\"_blank\"\u003e\n\u003cimg src=\"https://cdn.buymeacoffee.com/buttons/default-orange.png\" alt=\"Buy Me A Coffee\" height=\"41\" width=\"174\"\u003e\n\u003c/a\u003e\n\n### Basic Usage:\n\n[Click here to jump to the advanced Usage Docs below...](#flurlgraphql-usage)\n\n```csharp\nvar results = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n    .WithGraphQLQuery(@\"\n        query ($ids: [Int!], $friendsCount: Int!) {\n\t    charactersById(ids: $ids) {\n                personalIdentifier\n                name\n                friends(first: $friendsCount) {\n                    nodes {\n                        personalIdentifier\n                        name\n                    }\n                }\n            }\n        }\n    \")\n    .SetGraphQLVariables(new { ids = new[] { 1000, 2001 }, friendsCount = 2 })\n    .PostGraphQLQueryAsync()\n    .ReceiveGraphQLQueryResults\u003cStarWarsCharacter\u003e();\n```\n\n### Now Flurl v4.0+ compatible\nFlurlGraphQL is now fully updated to support Flurl v4.0+ with some significant performance improvements. Just as when upgrading to Flurl v4+, \nthere may be breaking changes such as those highlighted in the [Flurl upgrade docs](https://flurl.dev/docs/upgrade/).\n\n#### Key Changes are:\n - Namespace, Project/Library, and NuGet name has now been simplified to `FlurlGraphQL` (vs `FlurlGraphQL.Querying` in v1.x).\n - Default Json processing now uses `System.Text.Json` for serialization/de-serialization.\n   - The use of `System.Text.Json` brings along numerous changes associated with its use so it is best to refer to \n     [Microsoft's migration guide here](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft?pivots=dotnet-6-0).\n     - `System.Text.Json` processing with Json transformation strategy is now **~10X faster** than the original Newtonsoft.Json processing\n     - The new `Newtonsoft.Json` processing has also been optimized and which now benchmarks at **~2X faster** *-- using the new Json transformation strategy (vs Converter)*.\n - No longer need to manage Json configuration separately from the core Flurl configuration. We now dynamically initialize the Json Serializer using \n   the same settings/options as the core Flurl request.\n   - Therefore the previous GraphQL specific global configuration has been removed: `FlurlGraphQLConfig.ConfigureDefaults(config =\u003e config.NewtonsoftJsonSerializerSettings = ... )`\n - `Newtonsoft.Json` is still fully supported but requires explicitly referencing the `FlurlGraphQL.Newtonsoft` library also available on Nuget.\n   - To then enable `Newtonsoft.Json` processing you need to either:\n     1. Initialize your global Flurl settings and/or clients with the out-of-the-box Flurl `NewtonsoftJsonSerializer` via Flurl Global or Request level Configuration.\n        - Flurl Global Config Example: `FlurlHttp.Clients.UseNewtonsoft();`\n        - Flurl Request or Client level Example: `clientOrRequest.WithSettigns(settings =\u003e settings.JsonSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings()))...`\n        - Doing this will automatically implement Newtonsoft processing for any/all GraphQL requests as the defautl also.\n        - See Flurl docs for more info:  [Flurl.Http.Newtonsoft](https://github.com/tmenier/Flurl/tree/dev/src/Flurl.Http.Newtonsoft#flurlhttpnewtonsoft)\n     2. Or initialize it at the request level using the `.UseGraphQLNewtonsoftJson(...)` extension method available in ``FlurlGraphQL.Newtonsoft``.\n        - The Json processing can always be customized/overridden at the request level for any specific GraphQL Request to use either \n          `System.Text.Json` (via `.UseGraphQLSystemTextJson()`) or `Newtonsoft.Json` via (`.UseGraphQLNewtonsoftJson()`).\n   - Dynamics are now only supported when using `Newtonsoft.Json` which is consistent with Flurl v4+.\n - Retrieving the Raw Json responses now have dedicated APIs due to the different Json object models that each Json processing library uses.\n   - If using `System.Text.Json` then you must now use the `.ReceiveGraphQLRawSystemTextJsonResponse()` method which returns a `JsonObject`.\n   - If using `Newtonsoft.Json` then you must now use the `.ReceiveGraphQLRawNewtonsoftJsonResponse()` method which returns a `JObject`.\n\n\n## Performance with System.Text.Json vs Newtonsoft.Json\nThe `System.Text.Json` processing with the new Json transformation strategy is now **~10X faster** than the original `Newtonsoft.Json` processing in my tests; your results will vary and may be higher.\n\nAnd the newly optimized `Newtonsoft.Json` processing with new Json transformation strategy (vs Converter in original implementation) also now benchmarks **~2X faster**; a suprising benefit.\n\nThe following Benchmarks were run using .NET 6. And, as one might assume newer versions are likely even faster.\nFor example, .NET 4.6.1 is quite slow compared to .NET 6, and .NET 8 is noticeably faster.\n\n    // * Benchmark.NET Summary using .NET 6 *\n\n    BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3296/23H2/2023Update/SunValley3)\n    AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores\n    .NET SDK 8.0.200-preview.23624.5\n      [Host]     : .NET 6.0.28 (6.0.2824.12007), X64 RyuJIT AVX2\n      DefaultJob : .NET 6.0.28 (6.0.2824.12007), X64 RyuJIT AVX2\n\n\n    | Method                             | Mean       | Error    | StdDev   | Ratio |\n    |----------------------------------- |-----------:|---------:|---------:|------:|\n    | ParsingWithNewtonsoftJsonConverter | 2,517.0 ms | 27.39 ms | 24.28 ms |  1.00 |\n    | ParsingWithNewtonsoftJsonTransform | 1,246.9 ms |  9.73 ms |  9.10 ms |  0.50 |\n    | ParsingWithSystemTextJsonTransform |   282.8 ms |  5.60 ms |  7.48 ms |  0.11 |\n\n    // * Hints *\n    Outliers\n      FlurlGraphQLParsingBenchmarks.ParsingWithNewtonsoftJsonConverter: Default -\u003e 1 outlier  was  removed (2.58 s)\n\n    // * Legends *\n      Mean   : Arithmetic mean of all measurements\n      Error  : Half of 99.9% confidence interval\n      StdDev : Standard deviation of all measurements\n      Ratio  : Mean of the ratio distribution ([Current]/[Baseline])\n      1 ms   : 1 Millisecond (0.001 sec)\n\n\n## Nuget Packages (netstandard2.0, netstandard2.1, net6.0, and net461 compatible)\nTo use this in your project, add the [FlurlGraphQL](https://www.nuget.org/packages/FlurlGraphQL/) NuGet package to your project.\n\nTo use this in your project with `Newtonsoft.Json` processing then add the add the [FlurlGraphQL.Newtonsoft](https://www.nuget.org/packages/FlurlGraphQL.Newtonsoft/) NuGet package to your project.\n\n## Release Notes:\n### v2.0.5\n- Improve configuration support for Defeault Json Processing now with Enum Flags to make enabling/disabling the GraphQL Json Defaults much easier.\n\n### v2.0.4\n- Improve handling of Enums so that automatic processing as SCREAMING_CASE is handled now without the need to have [EnumMember(\"\")] attributes on every enum value when the names match.\n- Improve handling of GraphQL Serialization to now automatically use CamelCase for System.Text.Json \u0026amp; Newtonsoft.Json; it was already being handled when de-serializing but not when serializing.\n- Added new methods to help streamline the configuration of Json Serializer options/settings; a simple action/lambda can now be used to set Json Serialization Options/Settings.\n\n### v.2.0.3\n- Fix bug resulting in incorrect Exceptions when automatically enumerating (as IAsyncEnumerable) Connection Pages when a request returns with no results (NextPage = false \u0026amp; EndCursor = null).\n\n### v2.0.2\n- Fix issue with incorrect deserialization when using wrapper convenience class GraphQLEdge\u0026lt;T\u0026gt;.\n\n### v2.0.1\n- Fix issue with incorrect deserialization when using wrapper convenience class GraphQLEdge\u0026lt;T\u0026gt;\n\n### v2.0 (compatible with Flurl v4.0+) 🚀\n- Implement full support for Flurl v4.0+\n- Completely rewritten Json processing engine to now support both System.Text.Json \u0026amp; Newtonsoft.Json.\n- System.Text.Json processing with Json transformation strategy is now ~10X faster than the original Newtonsoft.Json processing.\n- Optimized Newtonsoft.Json processing with new Json transformation strategy (vs Converter) which now benchmarks at ~2X faster.\n\n### v1.3.1\n- Fixed bug in Error handling not identifying GraphQL Server errors correctly in all cases, and therefore not propagating the full details returned by the Server. \n- Fixed bug in Error handling not processing the error path variables correctly.\n\n### v1.3.1\n- Fixed Null reference issue in GraphQL/Request Error handling of HttpStatusCode.\n\n### v1.3.0\n- Added better support for Mutation handling so that single payload (per Mutation convention best practices) can be returned easily via `.ReceiveGraphQLMutationResult()`.\n  - This eliminates the need to use `.ReceiveGraphQLRawJsonResponse()` for dynamic Mutation response handling; but you may continue to do so if required.\n- Fixed bug to ensure Errors are returned on IGraphQLQueryResults when possible (not available on Batch Queries).\n- Fixed bug in processing logic for paginated requests when TotalCount is the only selected field on a paginated request; only affected CollectionSegment/Offset Paging requests.\n\n### v1.2.0\n- Added support to control the Persisted Query payload field name for other GraphQL servers (e.g. Relay server) which may be different than HotChocolate .NET GraphQL Server.\n- Added global configuration support via FlurlGraphQLConfig.ConfigureDefaults(config =\u003e ...) so that configurable options can be set once globlly with current support for Persisted Query Field Name and Json Serializer Settings.\n\n### v1.1.0\n- Added support for Persisted Queries via `.WithGraphQLPersistedQuery()` api.\n- Added support to execute GraphQL as GET requests via `.GetGraphQLQueryAsync()` api for edge cases, though `POST` requests are highly encouraged.\n- Improved consistency of the use of (optional) custom Json Serialization settings when `.SetGraphQLNewtonsoftJsonSerializerSettings()` is used to override the default GraphQL settings; \n\tnow initial query payloads sent also use these instead of the default Flurl settings. Some edge cases in GraphQL may require more advanced control over the Json serialization process \n\tthan the default Flurl Serializer interface offers and the default settings are internal and not accessible so we provide a way to control this for GraphQL specifically.\n\n### v1.0.0\n - v1.0.0 includes `FlurlGraphQL.Querying` api extensions and requires `Flurl v3.2.4` along with `Newtonsoft.Json`.\n - Initial release of the GraphQL Querying (only) extensions for Flurl.Http.\n - Supporting querying of typed data results with support for Relay based Cursor Paging, HotChocolate based Offset paging, Batch querying, Flurl style exception handling, \n     and simplified data models when using nested paginated results from GraphQL.\n - *NOTE: This does not currently support Subscriptions.*\n\n# FlurlGraphQL Usage\nThese GraphQL apis are an extension of the `Flurl.Http` library.\n\nFor core Flurl concepts check out the official [Flurl docs here](https://flurl.dev/docs/fluent-http/). \n\nOnce you have a GraphQL API endpoint url initialized, you will have access to the following:\n\n## Normal Flurl initialization of your GraphQL Endpoint Url\n```csharp\nvar graphqlUrl = \"https://graphql-star-wars.azurewebsites.net/api/graphql\".SetQueryParam(\"code\", azFuncToken)\n```\n\n## Initialize GraphQL Query or Persisted Query (Hash/ID)...\nAny calls to any of the GraphQL extensions will return a new `IFlurlGraphQLRequest` which exposes the unique features of GraphQL (vs simple REST API)\n\n```csharp\n//Inline your query, or load it from an embedded resource, etc.\n//This returns a new IFlurlGraphQLRequest which exposes the unique features of GraphQL (vs simple REST Api)\ngraphqlUrl.WithGraphQLQuery(\"...\");\n\n//When using Persisted queries you simply need to pass the query id or hash...\n//NOTE: It's common for these IDs to be hashes (unique for every change) but really they can be any label \n//          that is likely versioned when changed...\ngraphqlUrl.WithGraphQLPersistedQuery(\"AllCharactersWithFriendsPaginated-v1\");\n```\n\n## Set Query Variables...\n```csharp\ngraphqlUrl\n\t.SetGraphQLVariable(\"ids\", new[] {1001, 2001})\n\t.SetGraphQLVariable(\"friendsCount\", 2);\n\t\n//OR\ngraphqlUrl.SetGraphQLVariables(new { \n\tids = new[] {1001, 2001}, \n\tfriendsCount = 2 \n});\n```\n\n## Send your Query to the Server...\nCalls to execute the query with the Server will return a new `IFlurlGraphQLResponse` which exposes the unique features for processing GraphQL results (vs simple REST API).\n```csharp\n//Uses a POST request to execute the query with the server and returns an IFlurlGraphQLResponse...\n//NOTE: This is strongly encouraged api to use for many reasons (vs GET request below).\nvar graphqlResponse = await graphqlUrl.PostGraphQLQueryAsync();\n\n//Uses a GET request to execute the query with the server and returns an IFlurlGraphQLResponse...\n//NOTE: This HIGHLY DISCOURAGED since POST requests are far more resilient (no query size or variable limitations),\n//      but is provided for edge cases when a GET request must be used.\n//NOTE: Some GraphQL servers may not even support Variables with GET requests, though HotChocolate GraphQL for .NET does!\nvar graphqlResponse = await graphqlUrl.GetGraphQLQueryAsync();\n```\n\n## Receive your Results from the Response (simple results)...\nTo return results in the most simple form this provides an enumerable `IGraphQLQueryResults\u003cout TResult\u003e : IReadOnlyList\u003cTResult\u003e` of your typed results.\n```csharp\n//Get a simplified flattened enumerable set of typed results...\nvar graphqlResults = await graphqlResponse.ReceiveGraphQLQueryResults\u003cStarWarsCharacter\u003e();\n```\n\n## Receive Paginated Results from the Response (without Cluttering your Data Model)...\nPaginated structures in GraphQL is where the responses get more complex but processing them shouldn't be...\n\nThe paging apis encapsulate your typed results in a model that provides a greatly simplified facade and handles this as either `IGraphQLConnectionResults\u003cTResult\u003e` for Cursor based pagination or `IGraphQLCollectionSegmentResults\u003cTResult\u003e` for Offset based pagination.\n\nThese interfaces both expose `PageInfo` \u0026 `TotalCount` properties that may optionally be populated (if requested) along with some helpers such as `HasPageInfo()` or `HasTotalCount()`.\n\n### Cursor Paging Example to simply retrieve a single Page...\n\nCursor paging is the approach that is strongly recommended by GraphQL.org however, offset based paging (aka CollectionSegment - \n*using from HotChocolate .NET GraphQL Server naming convention*)) is availble also [(see below)](#offsetslice-paging-results-via-collectionsegment).\n\n```csharp\nvar results = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n    .WithGraphQLQuery(@\"\n        query ($first: Int, $after: String) {\n\t        characters(first: $first, after: $after) {\n                totalCount\n                    pageInfo {\n                    hasNextPage\n                    hasPreviousPage\n                    startCursor\n                    endCursor\n                }\n                nodes {\n                    personalIdentifier\n                    name\n\t            height\n                }\n            }\n        }\n    \")\n    .SetGraphQLVariables(new { first = 2 })\n    .PostGraphQLQueryAsync()\n    .ReceiveGraphQLConnectionResults\u003cStarWarsCharacter\u003e();\n```\n\n### Cursor Paging results via Connections...\nCursor based paging is handled by what GraphQL calls a [Connection](https://graphql.org/learn/pagination/#complete-connection-model), and the api is compliant with the [GraphQL.org recommended approach](https://graphql.org/learn/pagination/#connection-specification) provided by the [Relay specification for cursor paging](https://relay.dev/graphql/connections.htm).\n\n```csharp\n//Retrieve Cursor based Paginated results (aka Connection)...\nvar graphqlResults = await graphqlResponse.ReceiveGraphQLConnectionResults\u003cStarWarsCharacter\u003e();\n\n//Access the TotalCount (if requested in the query)...\nvar totalCount = graphqlResults.TotalCount;\n\n//Access the paging details easily (if requested in the query)...\nvar pageInfo = graphqlResults.PageInfo;\nvar hasNextPage = pageInfo.HasNextPage;\nvar hasPreviousPage = pageInfo.HasPreviousPage;\nvar startCursor = pageInfo.StartCursor;\nvar endCursor = pageInfo.EndCursor;\n\n//Enumerate the actual results of the page...\nforeach(var starWarsCharacter in graphqlResults)\n    Debug.WriteLine(starWarsCharacter.Name);\n```\n\n### Want access to the actual Cursor?\n```csharp\n//Use the provided GraphQLEdge\u003c\u003e class wrapper when Retrieving your Cursor based Paginated results (aka Connection)...\nvar graphqlResults = await graphqlResponse.ReceiveGraphQLConnectionResults\u003cGraphQLEdge\u003cStarWarsCharacter\u003e\u003e();\n\n//Enumerate the actual results of the page...\nforeach(var edge in graphqlResults)\n{\n    var starWarsCharacter = edge.Node;\n    var cursor = edge.Cursor;\n}\n\n//OR Implement the IGraphQLEdge interface on your models...\npublic class CursorStarWarsCharacter : StarWarsCharacter, IGraphQLEdge { /* Implement Cursor property */ };\n\n//OR simply add a Cursor Property (must be read/write) to your Model (IGraphQLEdge interface is not strictly needed)...\n//NOTE: You may also do this to access any other non-standard property that may be customized and added to the `Edge` by the GraphQL Server...\npublic class CursorStarWarsCharacter : StarWarsCharacter \n{\n    public string Cursor { get; set; }\n    public int CustomEdgeProperty { get; set; }\n};\n\n//Retrieve Cursor based Paginated results (aka Connection)...\nvar graphqlResults = await graphqlResponse.ReceiveGraphQLConnectionResults\u003cCursorStarWarsCharacter\u003e();\n\n//Enumerate the actual results which will also have the Cursor (if requested in the query)...\nforeach(var result in graphqlResults)\n    Debug.WriteLine($\"My Name is {result.Name} and my Cursor is [{result.Cursor}].\");\n\n```\n\n## Advanced Cursor Pagination (Retrieve or Stream ALL pages)\nThe api significantly simplifies the process of iterating through all pages of a GraphQL query and can either internally retreive all pages (returns an enumerable set of all pages), or allow streaming of the pages for you to handle.\n\n**NOTE:** _The streaming function is very efficient (esp. `AsyncEnumerable`) and actually pre-fetches the next page (if one exists) while you are processing the current page._\n\n### Retrieve All Cursor based Page results ...\n**NOTE: This will block and await while processing and retrieving all possible pages!**\n```csharp\n//Returns an IList\u003cIGraphQLConnectionResults\u003cTResult\u003e\u003e\n//NOTE: This will block and await while processing and retrieving all possible pages...\nvar graphqlPages = await graphqlResponse.ReceiveAllGraphQLQueryConnectionPages\u003cStarWarsCharacter\u003e();\n\n//Iterate the pages...\nforeach (var page in graphqlPages)\n{\n    page.HasAnyResults();\n    page.HasTotalCount();\n    page.PageInfo?.StartCursor\n    page.PageInfo?.EndCursor\n}\n\n//Gather All results for all the pages...\nvar allResults = graphqlPages.SelectMany(p =\u003e p);\n```\n\n### Stream All Cursor based Page results (netstandard2.1)...\n```csharp\n//Returns an IAsyncEnumerable\u003cIGraphQLConnectionResults\u003cTResult\u003e\u003e\nvar pagesAsyncEnumerable = graphqlResponse.ReceiveGraphQLConnectionPagesAsyncEnumerable\u003cStarWarsCharacter\u003e();\n\n//Stream the pages...\n//NOTE: Using this process to store the resulting page data to a Repository/DB would be done in \n//\ta streaming fashion minimizing the memory utilization...\nawait foreach (var page in pagesAsyncEnumerable)\n{\n    //... process the page data...\n}\n```\n\n### Stream All Cursor based Page results (netstandard2.0)...\nNOTE: AsyncEnumerable is not available in netstandard2.0, however we can still emulate the streaming to minimize our server utilization via `IEnumerable\u003cTask\u003c\u003e\u003e` \nwhich awaits each item as we enumerate.\n```csharp\n//Returns an IEnumerable\u003cTask\u003cIGraphQLConnectionResults\u003cTResult\u003e\u003e\u003e\nvar graphqlPagesTasks = await graphqlResponse.ReceiveGraphQLConnectionPagesAsEnumerableTasks\u003cStarWarsCharacter\u003e();\n\n//Enumerate the async retrieved pages (as Tasks) in a streaming fashion...\n//NOTE: Using this process to store the resulting page data to a Repository/DB would be done in \n//\ta streaming fashion minimizing the memory utilization...\nforeach (var pageTask in graphqlPagesTasks)\n{\n    var page = await pageTask;\n    //... process the page data...\n}\n```\n\n### Offset/Slice Paging results via CollectionSegment...\nOffset based paging is not recommeded by GraphQL.org and therefore is less formalized with [no recommended implemenation](https://graphql.org/learn/pagination/#pagination-and-edges). \nSo this api is compliant with the [HotChocolate .NET GraphQL Server] approach wich is fully GraphQL Spec compliant and [provides a formal implementaiton of Offset paging](https://chillicream.com/docs/hotchocolate/v12/fetching-data/pagination#offset-pagination) \nusing `skip`/`take` arguments to return a `CollectionSegment`.\nNOTE: However the HotChocoalte team also strongly encourages the use of Cursor Paging with Connections as the most flexible form of pagination.\n\n```csharp\n//Retrieve Offset based Paginated results (aka CollectionSegment)...\nvar graphqlResults = await graphqlResponse.ReceiveGraphQLCollectionSegmentResults\u003cStarWarsCharacter\u003e();\n\n//Access the TotalCount (if requested in the query)...\nvar totalCount = graphqlResults.TotalCount;\n\n//Access the paging details easily (if requested in the query)...\nvar pageInfo = graphqlResults.PageInfo;\nvar hasNextPage = pageInfo.HasNextPage;\nvar hasPreviousPage = pageInfo.HasPreviousPage;\n\n//Enumerate the actual results of the page...\nforeach(var starWarsCharacter in graphqlResults)\n    Debug.WriteLine(starWarsCharacter.Name);\n```\n\n## Advanced Offset Pagination (Retrieve or Stream ALL pages)\nJust as with Cursor pagination, the api significantly simplifies the process of iterating through all pages of a GraphQL query for Offset pagination results also.\nNOTE: The streaming function is very efficient (esp. `AsyncEnumerable`) and actually pre-fetches the next page (if one exists) while you are processing the current page.\n\n#### Retrive All Offset based Page results...\n**NOTE:** This will block and await while processing and retrieving all possible pages...\n```csharp\n//Returns an IList\u003cIGraphQLCollectionSegmentResults\u003cTResult\u003e\u003e\nvar graphqlPages = await graphqlResponse.ReceiveAllGraphQLQueryCollectionSegmentPages\u003cStarWarsCharacter\u003e();\n\n//Iterate the pages...\nforeach (var page in graphqlPages)\n{\n    page.HasAnyResults();\n    page.HasTotalCount();\n}\n\n//Gather All results for all the pages...\nvar allResults = graphqlPages.SelectMany(p =\u003e p);\n```\n\n#### Stream All Offset based Page results (netstandard2.1)...\n```csharp\n//Returns an IAsyncEnumerable\u003cIGraphQLCollectionSegmentResults\u003cTResult\u003e\u003e\nvar pagesAsyncEnumerable = graphqlResponse.ReceiveGraphQLCollectionSegmentPagesAsyncEnumerable\u003cStarWarsCharacter\u003e();\n\n//Stream the pages...\n//NOTE: Using this process to store the resulting page data to a Repository/DB would be done in \n//\ta streaming fashion minimizing the memory utilization...\nawait foreach (var page in pagesAsyncEnumerable)\n{\n    //... process the page data...\n}\n```\n\n#### Stream All Offset based Page results (netstandard2.0)...\nNOTE: AsyncEnumerable is not available in netstandard2.0, however we can still emulate the streaming to minimize our server utilization via `IEnumerable\u003cTask\u003c\u003e\u003e` \nwhich awaits each item as we enumerate.\n```csharp\n//Returns an IEnumerable\u003cTask\u003cIGraphQLCollectionSegmentResults\u003cTResult\u003e\u003e\u003e\nvar graphqlPagesTasks = await graphqlResponse.ReceiveGraphQLCollectionSegmentPagesAsEnumerableTasks\u003cStarWarsCharacter\u003e();\n\n//Enumerate the async retrieved pages (as Tasks) in a streaming fashion...\n//NOTE: Using this process to store the resulting page data to a Repository/DB would be done in \n//\ta streaming fashion minimizing the memory utilization...\nforeach (var pageTask in graphqlPagesTasks)\n{\n    var page = await pageTask;\n    //... process the page data...\n}\n```\n\n### Data Models with Nested Paginated results...\nIn GraphQL it's easy to expose nested selections of a result that itself is a paginated set of data. Thats why de-serializing this into\na normal model is complex and usually results in dedicated data models that are cluttered / polluted with unecessary elements such as `Nodes`, `Items`, `Edges`, or `PageInfo`, `Cursor`, etc.\n\nYou can still use these models if you like but in many cases with these nested data elements we primarily care about the results and\nwould like to keep a simplified model. This is handled by the api in that any `List\u003c\u003e` or `Array` in your data model (aka implements `ICollection`) that\nis mapped to a paginated result in the GraphQL response, will automatically be flattened. So if your query requested `Edges` or `Nodes` they\nare collected into the simplified model as a simple list of results without the need to complicate our data model.\n\nHere's an example of how we might just want the Friends as a list of results in our StarWarsCharacter (simple model), and we can select them via a complex nested,\nrecursive, paginated graph and this will be automatically handled:\n```csharp\n\npublic class StarWarsCharacter\n{\n    public string Name { get; set; }\n    //Recursive referencing set of Paginated Friends...\n    public List\u003cStarWarsCharacter\u003e Friends { get; set; }\n    public string Cursor { get; set; }\n}\n\nvar results = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n    .WithGraphQLQuery(@\"\n        query ($ids: [Int!], $friendsCount: Int!) {\n\t    charactersById(ids: $ids) {\n\t        friends(first: $friendsCount) {\n\t            nodes {\n\t                friends(first: $friendsCount) {\n\t                    nodes {\n\t                        name\n\t                        personalIdentifier\n\t                    }\n\t\t        }\n\t\t    }\n\t        }\n\t    }\n        }\n    \")\n    .SetGraphQLVariables(new { ids = new[] { 1000, 2001 }, friendsCount = 3 })\n    .PostGraphQLQueryAsync()\n    .ReceiveGraphQLQueryResults\u003cStarWarsCharacter\u003e();\n\nforeach (var result in results)\n{\n    //... process each of the 2 initial results by Id...\n    foreach (var friend in result.Friends)\n    {\n        //... process each of the nested friend results, but without the need for \n        //     our Model to be complicated by the fact that this is a paginated result from GraphQL...\n    }\n}\n```\n\n## Mutations...\nGraphQL provides the ability to create, update, delete data in what are called mutation operations. In addition, *mutations* are different from queries in that they\nhave different conventions and best practices for their implementations. In general, GraphQL mutations should take in a single *Input* and return a single \nresult *Payload*; the result payload may be a single business object type but is more commonly a root type for a collection of Results \u0026 Errors (as defined by the GraphQL Schema).\n\nDue to the complexity of all the varying types of Mutation Input \u0026 result Paylaod designs the API for mutations will fallback to parse the response as a single\nobject result model (as opposed to a an Array that a Query would return). Therefore, your model should implement any/all of the response Payload field features you are interested in.\n\nAnd if you need even more low level processing or just want to handle the Mutation result more dynamically then you can always use raw Json handling \nvia the ReceiveGraphQLRawJsonResponse API (see below).\n\n```csharp\nvar newCharacterModel = new CharacterModel()\n{\n    //...Populate the new Character Model...\n};\n\nvar json = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n    .WithGraphQLQuery(@\"\n        mutation ($newCharacter: Character) {\n            characterCreateOrUpdate(input: $newCharacter) {\n                result {\n                    personalIdentifier\n\t            name\n\t\t}\n\t\terrors {\n\t\t    ... on Error {\n\t\t        errorCode\n\t\t\tmessage\n\t\t    }\n\t\t}\n\t    }\n        }\n    \")\n    .SetGraphQLVariables(new { newCharacter: newCharacterModel })\n    .PostGraphQLQueryAsync()\n    //NOTE: Here CharacterCreateOrUpdate Result will a single Payload result (vs a List as  Query would return)\n    //      for which teh model would have both a Result property \u0026 an Errors property to be deserialized based\n    //      on the unique GraphQL Schema Mutation design...\n    .ReceiveGraphQLMutationResult\u003cCharacterCreateOrUpdateResult\u003e();\n```\n\n\n## Batch Querying...\nGraphQL provides the ability to execute multiple queries in a single request as a batch. When this is done each response is provided in the same order as requested\nin the json response object named the same as the GraphQL Query operation. The api provides the ability to retrieve all results (vs only the first by default) by\nretrieving the Batch results, which can then be handled one by one.\n\nEach query can be retrieved by it's index or it's operation string key name (*case-insensitive*).\n\nHere's an example of a batch query that uses an alias to run multiple queries and retrieve the results...\n```csharp\nvar batchResults = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n    .WithGraphQLQuery(@\"\n        query ($first: Int) {\n            characters(first: $first) {\n                nodes {\n\t            personalIdentifier\n\t\t    name\n\t            height\n\t\t}\n\t    }\n\n\t    charactersCount: characters {\n                totalCount\n\t    }\n        }\n    \")\n    .SetGraphQLVariables(new { first = 2 })\n    .PostGraphQLQueryAsync()\n    .ReceiveGraphQLBatchQueryResults();\n\nvar charactersResultsByName = batchResults.GetResults\u003cStarWarsCharacter\u003e(\"characters\");\nvar charactersResultsByIndex = batchResults.GetResults\u003cStarWarsCharacter\u003e(0);\nAssert.AreEqual(charactersResultsByName, charactersResultsByIndex);\n\nvar countResult = batchResults.GetConnectionResults\u003cStarWarsCharacter\u003e(\"charactersCount\");\nAssert.IsTrue(countResult.TotalCount \u003e charactersResultsByName.Count);\n```\n\n## RAW Json Handling (fully manual)...\nYou can always request the raw Json response that was returned by the GraphQL server ready to be fully handled manually (for off-the-wall edge cases). \nSimply use the respective API based on if you want are using System.Text.Json (via `.ReceiveGraphQLRawSystemTextJsonResponse()`) \nor Newtonsoft.Json (via `.ReceiveGraphQLRawNewtonsoftJsonResponse()`).\n\n*NOTE: You cannot receive `Newtonsoft.Json` raw json if the request was initialzied and executing using `System.Text.Json` serailizer and vice-versa; \na runtime exception will be thrown becuase this would be a large performance impact that would likely go unnoticed if it was allowed.*\n\n```csharp\n\n//For System.Text.Json Raw GraphQL response processing you will get a `JsonObject` result back for RAW Json handling!\nvar jsonObject = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n    .WithGraphQLQuery(@\"\n        query ($first: Int) {\n            characters(first: $first) {\n                nodes {\n\t            personalIdentifier\n\t\t    name\n\t            height\n\t\t}\n\t    }\n        }\n    \")\n    .SetGraphQLVariables(new { first = 2 })\n    .PostGraphQLQueryAsync()\n    .ReceiveGraphQLRawSystemTextJsonResponse();\n\n//OR For Newtonsoft.Json Raw GraphQL response processing you will get a `JObject` result back (just as with v1.x) for RAW Json handling!\nvar jsonObject = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n    .WithGraphQLQuery(@\"\n        query ($first: Int) {\n            characters(first: $first) {\n                nodes {\n\t            personalIdentifier\n\t\t    name\n\t            height\n\t\t}\n\t    }\n        }\n    \")\n    .SetGraphQLVariables(new { first = 2 })\n    .PostGraphQLQueryAsync()\n    .ReceiveGraphQLRawNewtonsoftJsonResponse();\n```\n\n## Error Handling...\nConsistent with the [spirit of Flurl for error handling](https://flurl.dev/docs/error-handling/#error-handling), errors from GraphQL will result \nin a `FlurlGraphQLException` being thrown with the details of the errors payload already parsed \u0026 provided as a helpful error message. However the raw error \ndetails are also available in the `GraphQLErrors` property.\n\n```csharp\ntry\n{\n    var json = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n        .WithGraphQLQuery(@\"\n            query (BAD_REQUEST) {\n\t        MALFORMED QUERY\n            }\n        \")\n        .PostGraphQLQueryAsync()\n        .ReceiveGraphQLRawJsonResponse();\n}\ncatch(FlurlGraphQLException graphqlException)\n{\n    //... handle the exception...\n    graphqlException.Message; //Message constructed from available details in the GraphQL Errors collection\n    graphqlException.Query; //Original Query\n    graphqlException.GraphQLErrors; //Parsed GraphQL Errors as Models that you can interrogate\n    graphqlException.ErrorResponseContent; //Original Error Response Json Text\n    graphqlException.InnerException; //The Original Http Exception\n}\n```\n\n## Need direct control over the Json Serialization Settings?\nNow with version 2.0 we dynamically inherit and implement all the settings from the base/core Flurl request! \nIn general, there is no need to explicitly set the settings for only GraphQL requests anymore, however you may continue to do so.\n\nNOTE: However to streamlinea and simplify the most common Json serialization issues we still enforce some Json processing defaults via \nthe `JsonDefaults` enum flags that can be set on the default configuration using the `JsonProcessingDefaults` property:\n - `EnableStringEnumHandling` -- Ensures that Enum String conversion is automatically handled as GraphQL uses sring enum values.\n - `EnableScreamingCaseEnums` -- Ensures that Enum strings are automatically converted to SCREAMING_CASE as is the default for GraphQL enums (e.g. HotChocolate GraphQL Server).\n   - This has no effect if the above `EnableStringEnumHandling` is disabled; unless you manually implement the screaming case naming policies/strategy (e.g. `FlurlGraphQLSystemTextJsonScreamingCaseNamingPolicy`).\n - `EnableCamelCaseSerialization` -- Ensures that the Json serialization is compatible with GraphQL JSON conventions which use camelCase and are usually case sensitive (e.g. HotChocolate GraphQL Server).\n - `EnableCaseInsensitiveJsonHandling` -- Ensures that Json de-serialziation is not case sensitive because C# conventions for `PascalCase` will nearly always fail due to JSON conventions for `camelCase`.\n   - NOTE: This *ONLY* applies to `System.Text.Json` because case-insensitive matching cannot be disabled with `Newtonsoft.Json`.\n - `EnableAll` -- a convenience flag used by default to ensure all of the above are enabled to greatly simplify the common pitfalls for Json handlingw between C# \u0026 GraphQL.\n\n\nWe still provide support to manually control the Json serializer settings specifically for individual GraphQL request processing.\n\n*NOTE: These settings will impact both how the initial query paylaod is serialized before being sent to the GraphQL server \nand how the response is parsed when being de-serailized back into your model.*\n\n```csharp\n    //Control how default Json Options/Settings are initialzied (for either System.Text.Json or Newtonsoft)\n    //NOTE: If Enabled, these global GraphQL Defaults will be enforced on any settings specified, even at the Request level.\n    FlurlGraphQLConfig.ConfigureDefaults(config =\u003e\n    {\n        config.JsonProcessingDefaults = JsonDefaults.EnableAll;\n        //Or Disable all default value enforcement so the system will use the Json Settings exactly as you specify in Flurl (or in Newtonsoft default configuration)\n        config.JsonProssingDefaults = JsonDefaults.None;\n        //Or some custom combination: this will remove Case-insensitivity, and CamelCase serialization....\n        config.JsonProssingDefaults = JsonDefaults.EnableStringEnumHandling | EnableScreamingCaseEnums;\n    });\n\n    //Override the Json Serialization Settings per request...\n    //NOTE: The above Defaults will be enforced on any settings you specify if enabled in the FlurlGraphQLConfig...\n    var json = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n        .WithGraphQLQuery(\"...\")\n        .UseGraphQLSystemTextJson(new JsonSerializerOptions() //\u003c== System.Text.Json Options!\n        {\n            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,\n            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,\n            WriteIndented = true,\n            //NOTE: THIS settings will not actually have any effect as the Framework always switches it ON to enable `case insensitive` processing.\n            //      This is due to the fact that GraphQL Json and C# often have different naming conventions for the Json so the vast majority of requests \n            //      would fail if not enabled automatically!\n            PropertyNameCaseInsensitive = true\n        })\n        .SetGraphQLVariables(...)\n        .PostGraphQLQueryAsync()\n        .ReceiveGraphQLRawSystemTextJsonResponse();\n\n    //OR for Newtonsoft.Json then you need to use the following for each request...\n    //NOTE: The above Defaults will be enforced on any settings you specify if enabled in the FlurlGraphQLConfig...\n    var json = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n        .WithGraphQLQuery(\"...\")\n        .UseGraphQLNewtonsoftJson(new JsonSerializerSettings() //\u003c== Newtonsof.Json Settings!\n        {\n            NullValueHandling = NullValueHandling.Ignore,\n            Formatting = Formatting.Indented\n        })\n        .SetGraphQLVariables(...)\n        .PostGraphQLQueryAsync()\n        .ReceiveGraphQLRawNewtonsoftJsonResponse();\n```\n\n## Need to control the Persisted Query Parameter Field Name?\n\nThe .NET HotChocolate GraphQL Server uses `id` for the Persisted query parameter name, however not all servers are consistent here.\nRelay for example uses `doc_id` (see [here for more details](https://chillicream.com/docs/hotchocolate/v13/performance/persisted-queries#client-expectations)).\n\nThis can be controlled per request, or globally by setting them in the FlurlGraphQL default configuration...\n```csharp\n    //Override the Persisted Query field name per request...\n    var json = await \"https://graphql-star-wars.azurewebsites.net/api/graphql\"\n        .WithGraphQLPersistedQuery(\"...\")\n        .SetPersistedQueryPayloadFieldName(\"doc_id\")\n        .SetGraphQLVariables(...)\n        .PostGraphQLQueryAsync()\n        .ReceiveGraphQLRawJsonResponse();\n\n\n    //Override the Persisted Query field name globally by setting it in the default configuration...\n    FlurlGraphQLConfig.ConfigureDefaults(config =\u003e\n    {\n        config.PersistedQueryPayloadFieldName = \"doc_id\";\n    });\n```\n","funding_links":["https://www.buymeacoffee.com/cajuncoding"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcajuncoding%2Fflurlgraphql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcajuncoding%2Fflurlgraphql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcajuncoding%2Fflurlgraphql/lists"}