DEV Community

Cover image for ZeroQL V3 - C# friendly GraphQL client
Stanislav Silin
Stanislav Silin

Posted on

ZeroQL V3 - C# friendly GraphQL client

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

Now we can use CLI like that:

dotnet zeroql -c ./zeroql.json
Enter fullscreen mode Exit fullscreen mode

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!]!
}
Enter fullscreen mode Exit fullscreen mode

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
//         }
//       }
//     }
//   ]
// }
Enter fullscreen mode Exit fullscreen mode

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!]!
}
Enter fullscreen mode Exit fullscreen mode

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!"
//       }
//     }
//   ]
// }
Enter fullscreen mode Exit fullscreen mode

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!]!
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
mathiash98 profile image
Mathias Haugsbø

Fantastic job with the library!