Stanislav Silin
Posted on July 18, 2023
Today I want to showcase the new version of the ZeroQL C# friendly GraphQL client with a Linq-like interface that allows to call GraphQL endpoints without the need to write raw GraphQL queries.
The new version brings a bunch of new features, such as improved initial configuration, compatibility with .Net Framework and Unity, a new way to pass variables into query, optional variables, improved scalar types handling, and the ability to get raw GraphQL query from C# lambda.
Github repository
Full Github wiki
Configuration improvements
ZeroQL's latest update introduces a major simplification to its configuration process, eliminating the need for manual setup and csproj file modifications. This change is centered around the removal of the previous necessity for ZeroQL Command Line Interface (CLI) usage for client generation.
In the past, the initial configuration process required executing the ZeroQL CLI to generate the client using the following command:
dotnet zeroql generate
--schema .\schema.graphql
--namespace TestServer.Client
--client-name TestServerGraphQLClient
--output Generated/GraphQL.g.cs
There was a way to simplify it by using the configuration file:
dotnet zeroql generate -c ./config.zeroql.json
With the new version, there is no need for it anymore. It is enough to generate the config file and add the ZeroQL package to the project. The ZeroQL will detect it and automatically execute generation on the build — no changes to csproj and custom scripts are required.
The generated GraphQL client would be placed inside the obj/ZeroQL
folder. In such a way, you would have access to the client and it would not be visible in the solution viewer or git.
.Net Standard Support
The new version brings support for the .Net Standard. It opens the possibility of using ZeroQL inside Xamarin, MAUI, Unity, and even legacy .Net Framework projects.
To make it work, you need to create a netstandard
class library and set the language version to the latest in your csproj
.
<LangVersion>latestmajor</LangVersion>
Subsequently, compatibility must be enabled within the ZeroQL configuration, which can be achieved with the following setting:
{
"netstandardCompatibility": true
}
That's all for classic netstandard
projects. The full documentation can be found here.
For Unity, setup is more complicated. More info on it can be found in wiki article
New variables
In previous versions, if you want to pass a variable, you need to create an object, pass it as a parameter, and reuse it inside the lambda:
var input = new { Id = 42 };
var response = client.Query(input, static (i, q) => q.User(i.Id, o => new { ... });
With the new version, there is no need for it. You can just pass a variable into lambda closure like that:
var id = 42;
var response = client.Query(q => q.User(id, o => new { ... });
This approach still has limitations. The variable should be the local variable or the method parameter.
Here is an example with the parameter:
public Task<User> GetUser(int userId)
{
var response = await client.Query(o => o.User(userId, o => new User(o.Id, o.FirstName, o.LastName)));
return response.Data;
}
Here is an example that would fail:
public int UserId { get; set; }
public Task<User> GetUser()
{
var response = await client.Query(o => o.User(UserId, o => new User(o.Id, o.FirstName, o.LastName))); // ZeroQL will report a compilation error here
return response.Data;
}
To be clear, you don't need actively account for it. ZeroQL will analyze and report errors if something is wrong.
Optional Variables
Now the optional variables work as expected. For example, let's have a look at the next GraphQL schema:
type Query {
users(page: Int!, size: Int!, filter: String): [User!]!
}
type User {
Id: ID!
Name: String!
}
In previous versions, if you needed to get users without filter, you would need to set it to null:
var page = 1;
var size = 10;
var response = await client.Query(q => q
.Users(page, size, null, o => new { o.Id, o.Name }));
Now you can skip it:
var page = 1;
var size = 10;
var response = await client.Query(q => q
.Users(page, size, selector: o => new { o.Id, o.Name }));
New Scalar Types
With ZeroQL, you can map GraphQL scalars into C# classes. There are two ways to do it.
First, just generate a ZeroQL GraphQL client. By default, all custom scalars will be mapped into stubs like that:
public sealed record Instant : ZeroQLScalar
{
public Instant()
{
}
public Instant(string value)
{
Value = value;
}
public static implicit operator Instant(string value) => new Instant(value);
public static implicit operator string (Instant scalar) => scalar.Value;
}
If you don't have any special requirements for a scalar, then this way is for you.
The second way allows you to define complex mappings for your scalars.
For example, we have a schema with scalar Instant
:
schema {
query: Query
}
type Query {
instant: Instant!
}
"Represents an instant on the global timeline, with nanosecond resolution."
scalar Instant
We can create a C# class and JSON serializer and add mapping for it:
public class Instant
{
public DateTimeOffset DateTimeOffset { get; set; }
}
public class InstantConverter : JsonConverter<Instant?>
{
public override Instant? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var instant = reader.GetString();
return new Instant { DateTimeOffset = DateTimeOffset.Parse(instant!) };
}
public override void Write(Utf8JsonWriter writer, Instant? value, JsonSerializerOptions options)
{
var instant = value.DateTimeOffset.ToString("yyyy-MM-ddTHH:mm:ssZ");
writer.WriteStringValue(instant);
}
}
public static class Module
{
[ModuleInitializer]
public static void Initialize()
{
// don't forget to add serializer to ZeroQL json options
ZeroQLJsonOptions.Configure(o => o.Converters.Add(new InstantConverter()));
}
}
Then during generation, pass parameter --scalars Instant=MyApp.Instant
or set property scalars in zeroql.json
config file
"scalars": {
"Instant": "MyApp.Instant"
}
It will replace the GraphQL scalar with the correct type in generated GraphQL client.
Get Raw GraphQL Query
Some GraphQL endpoints may accept raw GraphQL queries as arguments. Here example:
type Query {
user(id: ID!) User!
readInBatch(graphql: String!, variables: JSON): JSON!
}
type User {
id: ID!
name: String!
}
Now it is possible to generate GraphQL query without execution:
var queryInfo = QueryInfoProvider.Materialize<Query>(q => q
.User(id, u => new { u.Id, u.Name }));
// {
// "Query": "query ($id: ID!) { user(id: $id) { id name } }",
// "QueryBody": "($id: ID!) { user(id: $id) { id name } }",
// "OperationType": "query",
}
// then pass values into the query
var variables = // ...
var graphqlQuery = queryInfo.QueryBody;
var response = await client.Query(q => q.ReadInBatch(graphqlQuery, variables));
Final thoughts
In conclusion, the newest version of ZeroQL offers many features and improvements that enhance the usability of the GraphQL client. The simplified initial configuration process saves time and effort, while the extended .Net Standard support significantly broadens the client's potential usage. The new variables help to align expectations with a 'classic' LINQ-like interface.
Overall it is a huge update that makes building GrapgQL queries even easier.
The full source code can be found in the Github repository.
Have a look and press the start button. 😊
Posted on July 18, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.