GraphQL using .NET Boxed: Queries

This post is going to continue my exploration of GraphQL using the .NET Boxed template as a jumping off point. The code I am starting with can be found here. If you want to start at the very basics of getting the .NET Boxed templates installed, check out my ASP.NET Core with GraphQL using .NET Boxed post.

Finding a Thread

As stated in the last post the backing data for the project is in a static class found in the Repositories/Database.cs file. The data is Star Wars themed and consists of two lists of characters one for droid and one for humans which get combined into a list of characters.

Instead of starting with the data and finding where it is used I’m going to approach this from the perspective of the way the application handles requests. For an ASP.NET Core application, this means looking at the Configure function of the Startup class, which is where the HTTP pipeline is configured.

In the Configure function, we find the following two calls related to GraphQL. I dropped the actual setting of options out of both calls.

.UseGraphQLWebSocket<MainSchema>(new GraphQLWebSocketsOptions())
.UseGraphQLHttp<MainSchema>(new GraphQLHttpOptions())

Looks like MainSchema is the thread we needed to follow.

MainSchema

There isn’t a lot of code in the MainSchema class. The following is the full class as it was generated by the template.

public class MainSchema : Schema
{
    public MainSchema(
        QueryObject query,
        MutationObject mutation,
        SubscriptionObject subscription,
        IDependencyResolver resolver)
        : base(resolver)
    {
        this.Query = resolver.Resolve<QueryObject>();
        this.Mutation = mutation;
        this.Subscription = subscription;
    }
}

The Schema class that is the base class is provided by the GraphQL for .NET library. Just like any other schema in GraphQL it is used to define the data available to the client and the types of queries that can be used. For now, we are going to stick with following the Query type and leave Mutation and Subscription for future posts.

Query Object

For the template, we are using the schema for queries is located in the QueryObject class. Below is the class but simplified to only include the setup of one entity. For our sample Star Wars data, I am using the human bits and dropping the droids.

public class QueryObject : ObjectGraphType<object>
{
    public QueryObject(IHumanRepository humanRepository)
    {
        this.Name = "Query";
        this.Description = "The query type, represents all of the entry points into our object graph.";

        this.FieldAsync<HumanObject, Human>(
            "human",
            "Get a human by it's unique identifier.",
            arguments: new QueryArguments(
                new QueryArgument<IdGraphType>()
                {
                    Name = "id",
                    Description = "The unique identifier of the human.",
                }),
            resolve: context => humanRepository.GetHuman(
                context.GetArgument("id", 
                                    defaultValue: new Guid("94fbd693-2027-4804-bf40-ed427fe76fda")),
                context.CancellationToken));
    }
}

The class Constructor needs an instance of a class that is used to access the data for the entity the query is being defined for, in this case, an IHumanRepository.

The key bit in this class is the FieldAsync<HumanObject, Human> call. It is the definition of the query allowed for getting a single human. The first parameter is the name of the query, the second is the query’s description.

Next, are the arguments the query needs to execute.

arguments: new QueryArguments(
                new QueryArgument<IdGraphType>()
                {
                    Name = "id",
                    Description = "The unique identifier of the human.",
                })

In this example, the query can take a single argument for the ID of the human to be returned. The final bit is how this query should be resolved to the actual backing data.

resolve: context => humanRepository.GetHuman(
            context.GetArgument("id", 
                                defaultValue: new Guid("94fbd693-2027-4804-bf40-ed427fe76fda")),
            context.CancellationToken))

Here the context allows us to pull the parameters passed by the client using the context.GetArgument function call with name, as defined in the argument section, and use them in the call to the humanRepository.

Object Graph Type

The last point I want to touch on is the difference between the two types on the FieldAsync<HumanObject, Human> call. Human is the type the entity used by the data access. HumanObject is Human wrapped in an ObjectGraphType. The following is the full HumanObject class.

public HumanObject(IHumanRepository humanRepository)
{
    this.Name = "Human";
    this.Description = "A humanoid creature from the Star Wars universe.";
    this.Field(x => x.Id, 
                    type: typeof(IdGraphType))
              .Description("The unique identifier of the human.");
    this.Field(x => x.Name)
              .Description("The name of the human.");
    this.Field(x => x.HomePlanet, nullable: true)
              .Description("The home planet of the human.");
    this.Field<ListGraphType<EpisodeEnumeration>>
              (nameof(Character.AppearsIn), 
               "Which movie they appear in.");
    
    this.FieldAsync<ListGraphType<CharacterInterface>, 
                    List<Character>>(
        nameof(Human.Friends),
        "The friends of the character, or an empty list if they have none.",
        resolve: context => 
                 humanRepository.GetFriends(context.Source,
                                            context.CancellationToken));
    this.Interface<CharacterInterface>();
}

This pattern allows extra metadata to be added to the type being wrapped. You can see this work in the schema area playground that the sample application launches when in development mode.

Wrapping Up

My previous post on GraphQL left me feeling like it was pretty magical. That is true from the client side perspective. From the server side, that magic comes with some complexity. Don’t take that as a judgment on GraphQL it is just one of the aspects that must be considered before selecting a technology. As I get more familiar with the server side of GraphQL I’m sure some of what I am seeing as complexity when just learning will become clear.

The associated sample code can be found here.


Also published on Medium.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.