ZeroQL - LINQ Graphql Client Updates

byme8

Stanislav Silin

Posted on July 18, 2023

ZeroQL - LINQ Graphql Client Updates

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

There was a way to simplify it by using the configuration file:

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

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

Subsequently, compatibility must be enabled within the ZeroQL configuration, which can be achieved with the following setting:

{
    "netstandardCompatibility": true
}
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

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

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

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

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

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

Then during generation, pass parameter --scalars Instant=MyApp.Instant or set property scalars in zeroql.json config file

"scalars": {
    "Instant": "MyApp.Instant"
}
Enter fullscreen mode Exit fullscreen mode

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

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

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. 😊

💖 💪 🙅 🚩
byme8
Stanislav Silin

Posted on July 18, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related