ZeroQL V3 - C# friendly GraphQL client
Stanislav Silin
Posted on February 28, 2023
Hello there! I want to share the new features of ZeroQL's latest release. This release contains many improvements, including support for unions and interfaces, automatic scalars generation, and config file support.
Introducing ZeroQL's Config File
With the latest release, ZeroQL introduces support for config files. This means that you no longer have to memorize CLI options or type long commands every time you want to generate a GraphQL client. You can now create a configuration file with all your desired options.
Let's suppose we want to execute the next command:
dotnet zeroql generate --schema .\schema.graphql --namespace TestServer.Client --client-name TestServerGraphQLClient --output Generated/GraphQL.g.cs
We can use dotnet zeroql config init
. It will create the zeroql.json
file with the following content:
{
"$schema": "https://raw.githubusercontent.com/byme8/ZeroQL/main/schema.verified.json",
"graphql": "./schema.graphql",
"namespace": "TestServer.Client",
"clientName": "TestServerGraphQLClient",
"output": "./Generated/GraphQL.g.cs"
}
Now we can use CLI like that:
dotnet zeroql -c ./zeroql.json
Unions and interfaces
ZeroQL's latest update brings proper support for unions and interfaces. This means that you can now work with these GraphQL types in your C# code using an intuitive and straightforward syntax.
To demonstrate this feature, let's take a look at an example GraphQL schema containing interfaces:
schema {
query: Query
}
interface IFigure {
perimeter: Float!
}
type Circle implements IFigure {
center: Point!
radius: Float!
perimeter: Float!
}
type Point implements IFigure {
x: Float!
y: Float!
perimeter: Float!
}
type Square implements IFigure {
topLeft: Point!
bottomRight: Point!
perimeter: Float!
}
type Query {
figures: [IFigure!]!
}
Now we can get figures by writing the next C# query:
var response = await qlClient.Query(static q => q
.Figures(f => new
{
f.Perimeter,
Circle = f.On<Circle>().Select(c => new
{
c.Radius,
Center = c.Center(p => new { p.X, p.Y })
}),
Square = f.On<Square>().Select(s => new
{
TopLeft = s.TopLeft(p => new { p.X, p.Y }),
BottomRight = s.BottomRight(p => new { p.X, p.Y })
})
}));
Console.WriteLine(JsonSerializer.Serialize(response));
// {
// "Query": "query { figures { perimeter ... on Circle { radius center { x y } } ... on Square { topLeft { x y } topLeft { x y } } __typename } }",
// "Data": [
// {
// "Perimeter": 6.2831854820251465,
// "Circle": {
// "Radius": 1,
// "Center": {
// "X": 1,
// "Y": 1
// }
// }
// },
// {
// "Perimeter": 40,
// "Square": {
// "TopLeft": {
// "X": 1,
// "Y": 1
// },
// "BottomRight": {
// "X": 11,
// "Y": 11
// }
// }
// }
// ]
// }
Basically, every interface is treated as a C# interface. Then we can use the extension method On
to select the concrete implementation we want.
The same logic applies to unions too. Here is the GraphQL schema example:
schema {
query: Query
}
type TextContent {
text: String!
}
type ImageContent {
imageUrl: String!
height: Int!
}
union PostContent = TextContent | ImageContent
type Query {
posts: [PostContent!]!
}
And here is how we can write a query to get a result:
var response = await qlClient.Query(static q => q
.Posts(
o => new
{
Image = o.On<ImageContent>().Select(oo => new
{
oo.ImageUrl,
oo.Height
}),
Text = o.On<TextContent>().Select(oo => new
{
oo.Text
}),
}));
Console.WriteLine(JsonSerializer.Serialize(response));
// {
// "Query": "query { posts { ... on ImageContent { imageUrl height } ... on TextContent { text } __typename } }",
// "Data": [
// {
// "Image": {
// "ImageUrl": "http://example.com/image.png",
// "Height": 1920
// }
// },
// {
// "Text": {
// "Text": "Hello World!"
// }
// }
// ]
// }
As you can see on the C# level, there is no difference between the interface and the union types. All of them have identical syntax and behavior.
Automatic Scalar Generation
ZeroQL now supports automatic scalar generation, which means that you no longer have to define custom types and JSON serializers for every scalar type you use in your GraphQL schema. This feature automatically generates C# types and JSON serializers for every scalar type used in your schema, making it easier and faster to work with GraphQL.
To use this feature, you simply define your scalar types in your GraphQL schema like this:
schema {
query: Query
}
scalar PostId;
type Post {
id: PostId,
text: String!
imageUrl: String!
height: Int!
}
type Query {
myPosts: [Post!]!
}
When you generate your client using ZeroQL, it will automatically generate a C# record for the PostId
scalar type, along with a JSON serializer. This makes it easy to work with scalars in your C# code without manually defining custom types and serializers. In this case, it would look like that:
public sealed record PostId : ZeroQLScalar
{
public PostId()
{
}
public PostId(string value)
{
Value = value;
}
public static implicit operator PostId(string value) => new PostId(value);
public static implicit operator string (PostId scalar) => scalar.Value;
}
As a result, you can have as many scalars as you want, and all of them will have a proper representation on the C# level automatically.
Conclusion
It is a solid update for ZeroQL. It extends support for GraphQL capabilities, simplifies configuration management, and, at the same time, maintains the outstanding performance of a raw HTTP request.
If you have any questions or want to find more detailed instructions on how to use the library, you can find them on Github.
Posted on February 28, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.