PolyglotDataStudio.GraphQL
1.0.0
See the version list below for details.
dotnet add package PolyglotDataStudio.GraphQL --version 1.0.0
NuGet\Install-Package PolyglotDataStudio.GraphQL -Version 1.0.0
<PackageReference Include="PolyglotDataStudio.GraphQL" Version="1.0.0" />
<PackageVersion Include="PolyglotDataStudio.GraphQL" Version="1.0.0" />
<PackageReference Include="PolyglotDataStudio.GraphQL" />
paket add PolyglotDataStudio.GraphQL --version 1.0.0
#r "nuget: PolyglotDataStudio.GraphQL, 1.0.0"
#:package PolyglotDataStudio.GraphQL@1.0.0
#addin nuget:?package=PolyglotDataStudio.GraphQL&version=1.0.0
#tool nuget:?package=PolyglotDataStudio.GraphQL&version=1.0.0
PolyglotDataStudio.GraphQL
A lightweight, strongly-typed GraphQL schema builder and runtime helpers for .NET.
PolyglotDataStudio.GraphQL provides building blocks (object/input/scalar primitives and root operations) to define GraphQL types and execute queries in a test-friendly, framework-agnostic way. The library is designed to be easy to use from unit tests and integrates cleanly with .NET Standard 2.1 and .NET 10 applications.
Create a GraphQL schema by hand, or use the accompanying CLI tool to generate C# types from an existing GraphQL schema definition file.
Purpose
- Provide first-class, composable types to declare GraphQL objects, input objects and scalars in C#.
- Keep schema definition concise and type-safe.
- Make writing unit tests for GraphQL schemas straightforward (see
PolyglotDataStudio.GraphQL.Testsfor working examples). - Be small, dependency-free and easy to include in libraries or microservices.
Highlights / Features
- Core entity base types (
GraphQLEntityBase,GraphQLObject,GraphQLInputObject,GraphQLRootOperation). - Common scalar implementations (
GraphQLScalarString,GraphQLScalarInt,GraphQLScalarFloat,GraphQLScalarBool). - Helpers for building test schemas and executing queries from unit tests.
- Targets .NET Standard 2.1 and .NET 10 for broad compatibility.
- Ultra-high performance, with no runtime reflection, expressions or dynamic code generation
Installation
Install both the CLI tool and the library package.
dotnet tool install PolyglotDataStudio.GraphQL.CLI
dotnet add package PolyglotDataStudio.GraphQL
The CLI tool is used to generate C# types from a GraphQL schema definition file. The library package contains the runtime types and helpers. See the PolyglotDataStudio.GraphQL.CLI README for details on using the CLI tool.
Quick Start
Use the CLI tool to generate C# types from a GraphQL schema file. The output from the tool can be directly included in your code, or packaged into a separate dependent library, at your discretion.
Let's assume your schema has a Type called Hero with fields id and name.
The following code creates a simple query:
var query = new Query();
query.Hero.WithFields( h => h.Id, h => h.Name );
var gql = query.Render( formatted: false );
The resulting GraphQL query string will be:
query { hero { id name } }
... or with formatting turned on:
query {
hero {
id
name
}
}
The WithFields method accepts multiple field selectors, so you can specify all desired fields in a single call,
or if your query requires a complex retrival from a deeply nested structure, you can chain multiple WithFields
calls together. Also, you can name your query and apply field aliases as needed.
var query = new Query().WithName( "GetHero" );
query.Hero.WithFields( h => h.Id )
.WithFields( h => h.Name.WithAlias( "humanName" ) );
var gql = query.Render( formatted: true );
Which produces the following GraphQL query string:
query GetHero {
hero {
id
humanName: name
}
}
Generating multiple queries can be done by executing additional calls against the same root operation.
var query = new Query();
query.Hero.WithAlias( "justId" ).WithFields( h => h.Id );
query.Hero.WithAlias( "justName" ).WithFields( h => h.Name );
var gql = query.Render( formatted: true );
Which produces the following GraphQL query string:
query {
justId: hero {
id
}
justName: hero {
name
}
}
Parsing response data
To handle response data, an additional layer of types are generated in the CLI to better support JSON deserialization. These
are emitted into the Models child namespace, below the namespace nominated in the command-line.
Some important information about the generated models follows:
- Custom scalars are handled as strings, as the SDL specification does not support specifying an underlying type.
- It is necessary to manually specify type descriminators and descendent types for interfaces, as this information
cannot be determined automatically. For this reason Interfaces are generated as
partial. - Model classes are always generated with the same class name as their main counterpart, with the addition of a
Modelsuffix. - To ensure there are no typename collisions, all model types are generated into a separate
Modelsnamespace.
Simple expample
Using the previous Hero example, the code to generate a query and parse the response with the default modes is as follows:
var query = new Query();
query.Hero.WithFields( h => h.Id, h => h.Name );
var gql = query.Render( formatted: true );
// use your own infrastructure for actually executing the query
var responseJson = await myGqlExecutor.ExecuteQuery( gql );
query.SetResultJson( resultJson );
Models.Hero myHero1 = query.GetResult();
// alternative 1
var myHero2 = query.GetResult<Models.Hero>();
// alternative 2
var myHero2 = query.GetResult<MySpecialType>();
SetResultJson()accepts a raw json string or aJsonElement- Don't set field aliases or model properties will not map correctly
- You are free to use any model type you wish, defaults are provided automatically
GetResult<T>()provides an additional argument to override the serializer options if you wish
More complex expample
For a more complex example we are going to make multiple operations in a single request. To deserialize the response we will need to custom-make a type that combines the results from both queries. While possible, this is not always desirable.
As an alternative, provide the GetResult() method with a string parameter to indicate which operation (or alias) we want the
response for, and use the default or a custom model to represent part of the respose.
When querying the same type multiple times, it is important to set field aliases to distinguish the responses.
var query = new Query();
query.Hero.WithAlias( "justId" ).WithFields( h => h.Id );
query.Hero.WithAlias( "justName" ).WithFields( h => h.Name );
var gql = query.Render( formatted: true );
// use your own infrastructure for actually executing the query
var responseJson = await myGqlExecutor.ExecuteQuery( gql );
query.SetResultJson( resultJson );
// alternative 1
var myHeroId = query.GetResult<Models.Hero>( "justId" );
var myHeroName = query.GetResult<Models.Hero>( "justName" );
Example use of interfaces and poymorphism during deserialization
Let's assume your schema defines the following elements:
enum AccountType {
USER
ROLE
}
interface Account {
displayName: String
name: String!
accountType: AccountType!
}
type User implements Account {
displayName: String
name: String!
accountType: AccountType!
email: String
phone: String
}
type Role implements Account {
displayName: String
name: String!
accountType: AccountType!
isAdministrator: Boolean
}
The generated C# models will be created as follows:
public enum AccountType { ROLE, USER }
public partial interface AccountModel {
string? DisplayName { get; }
string? Name { get; }
AccountType? AccountType { get; }
}
public class UserModel : AccountModel {
public string? DisplayName { get; set; }
public string? Name { get; set; }
public AccountType? AccountType { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
}
public class RoleModel : AccountModel {
public string? DisplayName { get; set; }
public string? Name { get; set; }
public AccountType? AccountType { get; set; }
public bool? IsAdministrator { get; set; }
}
- Model members always support nullability to accommodate partial responses.
- Polymorphism support is built in, insofar as concrete types exist for all interfaces in your schema. When a request is made for a property
that returns an interface, the framework will include the
__typenamefield automatically, to use as a type discriminator, which will select the correct concrete type to use in deserialization, assuming one is available.
To support the manual deserialization of polymorphic types, one might create an additional partial class as follows:
[JsonPolymorphic( IgnoreUnrecognizedTypeDiscriminators = true, TypeDiscriminatorPropertyName = "accountType", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor )]
[JsonDerivedType( typeof( UserModel ), "USER" )]
[JsonDerivedType( typeof( RoleModel ), "ROLE" )]
public partial interface AccountModel { }
This allows the deserializer to correctly instantiate the appropriate derived type based on the accountType field in the response data.
While it would be possible to specify some of the JsonDerivedType attributes in the generated code, it is not possible to know which field could be
used as the type discriminator, or which values would identify the type to use, so this must be done manually.
Contributing
Contributions are welcome. Please:
- Open issues for bugs or feature requests.
- Submit pull requests against the
developbranch. - Follow repository coding conventions and add/update unit tests for new behavior.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. 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 was computed. 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. net9.0 was computed. 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. |
| .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. |
-
.NETStandard 2.1
- System.Text.Json (>= 10.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on PolyglotDataStudio.GraphQL:
| Package | Downloads |
|---|---|
|
PINGWorks.SitecoreExperienceEdge.ContentSDK
PING Works' .NET SDK for working with Sitecore's Experience Edge Content API. |
GitHub repositories
This package is not used by any popular GitHub repositories.