GraphQL with .NET Core 3.0

GraphQL with .NET Core 3.0

In the previous blog post, I described what GraphQL is and why you might want to use it. Hopefully, you're now reading this blog post because you're eager to learn more about GraphQL (and so was I). This blog post will cover the technical bits on how to set up a GraphQL endpoint using .NET Core 3.0.

The best way to learn GraphQL for .NET is to build a GraphQL endpoint. Therefore I will take you through the steps that are required to build a GraphQL endpoint. As an example, I've built GraphOfThrones. This API will expose data about Game Of Thrones characters and episodes. The image below shows the schema for this endpoint:

GraphOfThrones example Schema

Setting up the solution

The endpoint will be hosted as a .NET Core web application, so we'll start with creating an empty Web app project (ASP.NET Core API 3.0). I prefer to separate the GraphQL code from the Web files, so I've also created a .netstandard library project. After creating the projects, we need to install some NuGet packages:

Building the GraphQL schema

For clients to know what data they can get and what actions they can perform, we need to build a schema. This schema will contain a Query, Mutation, and a subscription (as can be seen in the image above). Creating a schema is pretty straight forward:

public class GOTSchema : GraphQL.Types.Schema
{
    public GOTSchema(IDependencyResolver resolver) : base(resolver)
    {
        Query = resolver.Resolve<Query>();
        Mutation = resolver.Resolve<Mutation>();
        Subscription = resolver.Resolve<Subscription>();
    }
}

Setting the Query, Mutation, and Subscription properties on the Schema class will expose these objects to the clients. As you might notice from the code above, GraphQL for .NET uses the DepedencyResolver to resolve all types. Therefor all types related to GraphQL need to be registered with the dependency container (see example).

Query
Just like with the schema, we also need to create a class for Query. All classes that are exposed in the GraphQL endpoint, inherit from ObjectGraphType. To use this type as a Query, de name has to be equal to "Query". In the example of GraphOfThrones, I'm exposing a query containing 2 fields: characters & episodes. These fields are of type ListGraphType, which means the will return a list of items. The method to set up these fields needs two parameters: Name & resolver. The name is what can be used by the client to query this data, the resolver will actually get the data (more on this later).

public class Query : ObjectGraphType<object>
{
    public Query(IService<Character> characterService, IService<Episode> episodeService)
    {
        Name = "Query";
        // Expose characters
        Field<ListGraphType<CharacterType>>("characters", resolve: (context) => characterService.GetAll());
        // Expose episodes
        Field<ListGraphType<EpisodeType>>("episodes", resolve: (context) => episodeService.GetAll());
    }
}

Mutation
Just like with Queries, we need to create a class and set the Name property correctly and then add the Fields in the constructor. Each field will be a mutation that can be called from the client. In the example project, there are two mutations: addEpisode & killCharacter. Based on the name it's probably pretty obvious what these mutations will do.


 public class Mutation : ObjectGraphType<object>
 {
    public Mutation(IEpisodeService epiSvc)
    {
        Name = "Mutation";

        Field<EpisodeType>(
             "addEpisode", // name of this mutation
             arguments: new QueryArguments(
             new QueryArgument<NonNullGraphType<AddEpisodeRequest>> 
             { 
                Name = "episode" 
             }), // Arguments
             resolve: context =>
             {
                 // Get Argument 
                 var rqst = context.GetArgument<AddEpisodeRequest>("episode");
                 // Create new Episode and return the object
                 var episode = new Episode 
                 { 
                     episodeTitle = rqst.Name, 
                     episodeDescription = rqst.Description 
                 };
                 return epiSvc.Create(episode);
             }
         );
    }
}

For the sake of readability, only the addEpisode Mutation is shown above. The Generic Type that is used (<EpisodeType>), will be the return type for this Mutation. Similar to Queries, the client can request a range of fields, instead of getting the entire object.

The first parameter will be the unique name for this Mutation. It can be used to invoke this Mutation. The second parameter defines what arguments the Mutation expects. In this case, the AddEpisodeRequest is required because it is wrapped inside a NonNullGraphType. Finally, you'll need to pass the resolver function which will contain the code to be executed. The resolver needs to return an instance of the generic type that's defined, in this example that is EpisodeType.

Subscriptions
To expose Subscriptions in your GraphQL schema, you'll need to create a class that also inherits from ObjectGraphType<object> and has the Name property set to "Subscription". When the class is created, you can add Subscriptions with the AddField function that takes in one parameter of EventStreamFieldType class. This class contains a few properties that need to be set for Subscriptions to work:

  • Name: This is the name that can be used to subscribe to notifications from this subscription.
  • Type: The type of the class that will be used to broadcast notifications.
  • Resolver: You can set a function as Resolver to transform data (internal model) to the specified GraphQL type. The GraphQL for .NET library contains a FuncFieldResolver that can be used (see example below).
  • Subscriber: This property takes any implementation for the IEventStreamResolver and will take care of registering clients and notifying them. The easiest way to implement this is to use the EventStreamResolver<T> class. In the constructor of this class, you need to pass a function that returns a subscription (IObservable). More details in the code below ↓.
  • Arguments (optional): A list of arguments that can be passed when registering for a subscription. This can be used to register for specific notifications (filtering).

The code below displays how to create the Subscription class itself:

public class Subscription : ObjectGraphType<object>
{
    public Subscription(IEpisodeService episodeService)
    {
        Name = "Subscription";

        AddField(new EventStreamFieldType
        {
            Name = "episodeAdded",
            Type = typeof(EpisodeAddedType),
            Resolver = new FuncFieldResolver<Episode>((context) => context.Source as Episode),
            Subscriber = new EventStreamResolver<Episode>((context) => episodeService.EpisodeAdded())
        });
    }
}

As you might notice, the subscriber class uses the IEpisodeService to handle subscriptions. You can implement this in any way you would like, but to show how easy it can be, I've created a sample using Reactive Extensions:

public class EpisodeService : ServiceBase<EpisodeResult>, IEpisodeService
{
    private readonly ISubject<Episode> _sub = new ReplaySubject<Episode>(1);

    public Episode Create(Episode obj)
    {
        [...]
        _sub.OnNext(obj);
        return obj;
    }

    public IObservable<Episode> EpisodeAdded()
    {
        return _sub.AsObservable();
    }        
}

This code is using a Subject to raise notifications. In the EpisodeAdded() method, this Subject is returned as an Observable, which allows clients to register for notifications. When an Episode is added, the OnNext method will be called on the Subject (_sub). This will notify all the observers of this event. If you want to know more about the Observer pattern, please visit the Microsoft docs.

Types
An important part of the schema is Types. Types define the object structure that your endpoint will expose.

All Types inherit from the ObjectGraphType<T>. The generic type (<T>) that can be passed, should be the internal model type. If you provide this type, the resolver can map properties from your internal model, to the fields of your GraphQL model. If you don't have an internal model, you can just use the type object and do the mapping yourself.


public class CharacterType : ObjectGraphType<Character>
{
    public CharacterType(IService<Episode> episodeService)
    {
        Field(c => c.characterName);
        Field(c => c.parents);
        Field(c => c.siblings);
        Field(c => c.characterImageFull);
        Field(c => c.characterImageThumb);
        Field(c => c.killed);
        Field(c => c.killedBy);
        Field<ListGraphType<EpisodeType>>("episodes", resolve: (context) => GetAllEpisodesForCharacter(episodeService, context.Source.characterName));
    }
}

Every field that you add to your GraphQL type, has a Resolver function. This resolver function will return the value for the field. This makes it very flexible because you can resolve from different kinds of sources per Field. If a field is not requested in the query, the resolver will not get called.

Having to implement a resolver function for all fields might get messy and that's where ObjectGraphType<T> comes in handy. This class contains a method that allows you to map the properties of your internal model to the fields of your GraphQL type. In the sample above, I've used the helper on all lines except for the last line in the constructor. If you don't provide a name, the name of the property on the internal model will be used. The last line contains a field called "episodes" that has a custom resolver. The custom resolver is using the EpisodeService to fetch additional data. To apply filters on the list of episodes, the characterName is fetched from the Context.Source.

Wiring up the endpoint

After defining the schema, we need to add some plumbing code to host the GraphQL endpoint in our .NET Core app. We can add this code to the Startup class that was generated for us:

  1. Register Dependencies: Make sure to register all types that are referenced in your GraphQL schema. You can Register these types in the ConfigureServices function, using the default Dependency Container from .NET Core:
serviceCollection.AddSingleton<Query>();
  1. Register the types from GraphQL.NET:
serviceCollection.AddGraphQL(options =>
{
    options.EnableMetrics = true;
    options.ExposeExceptions = true;
}).AddWebSockets();

Note: Enabling metrics and exposing exceptions might help with debugging. It's not recommended for production.

  1. Finally, you'll need to enable WebSockets (if you're using Subscriptions) and the GraphQL endpoint (with schema):
// This will enable WebSockets in Asp.Net core
app.UseWebSockets();

// Enable endpoint for websockets (subscriptions)
app.UseGraphQLWebSockets<GOTSchema>("/graphql");
// Enable endpoint for querying
app.UseGraphQL<GOTSchema>("/graphql");

With these steps, you should be able to run your GraphQL endpoint. Pretty easy, huh?! If you are having issues setting up your endpoint, feel free to check out the source code on GitHub.

GraphiQL

Although GraphiQL is not required to host an endpoint, it's highly recommended to use this. GraphiQL offers a user interface that allows clients to browse through an endpoint. It also generates documentation about your schema and offers a playground to try out queries, mutations or subscriptions. It can be best described as Swagger for GraphQL, only fancier.

The easiest way to enable GraphiQL is by installing the NuGet package. When this is installed, you can enable it with the following code:

app.UseGraphiQLServer(new GraphiQLOptions());

More information can be found on the GitHub page.

As an alternative, you can also choose to place the GraphiQL HTML, CSS and Javascript files into the WWWRoot folder of the Web project. An example of these files can be found on GitHub. This approach also requires you to enable StaticFiles in your .NET Core web application. You can do this by installing Microsoft.AspNetCore.StaticFiles and adding the following lines of code to the Configure method in Startup.cs:

app.UseDefaultFiles();
app.UseStaticFiles();

What's next?

Creating a GraphQL endpoint with .NET Core 3.0 can be pretty exciting, but it would be even more exciting if you can also consume this in client applications. The next blogpost in this series will cover how to host an GraphQL endpoint in Azure and how to consume this from a Xamarin app.

Bas de Cort

About Bas de Cort

Bas is a software developer from The Netherlands with a great passion for mobile and innovative technology. This blog will mostly cover mobile technology, especially Xamarin.

Comments