We will start by creating a blank ASP.Net Core App. Gradually we will create a middleware that can handle GraphQL requests.

dotnet new web 

If you are familiar with ASP.NET Core middleware, you may have noticed in Startup.cs, that it responses back "Hello World!" on application start-up. Here, UseEndpoints is a middleware that executes an endpoint associated with the current request.

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/", async context =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
});
Startup.cs

Practically a middleware is a delegate or more precisely; a request delegate. As the name suggests, it handles an incoming request and decides whether or not to delegate it to the next middleware in the pipeline. In our case, we configured a request delegate using the Run() (extension) method of IApplicationBuilder. Between three extension methods (Use, Run, Map), Run() terminates the request for further modifications in the request pipeline.

The code in our console application was very simple and can only respond to a result of a hardcoded static query. However, in a real-world scenario, the query should be dynamic hence we must read it from the incoming request.

It's a rule of thumb that all GraphQL requests (either query/mutation/subscription) should be posted over HTTP POST method. So, we change the MapGet method to MapPost. We also update the endpoint to a more specific one rather than the default root path ("/").

app.UseEndpoints(endpoints =>
{
    endpoints.MapPost("/graphql", async context => {});
});
Startup.cs

A request body can contain a whole lot of fields, but let's say the passed in query comes within a field named query. So we can parse out the JSON string content of the body into a complex type that contains a Query property,

public class GraphQLRequest
{
    public string Query { get; set; }
}
GraphQLRequest.cs

Every request delegate accepts a HttpContext. If the query is posted over an HTTP request, you can easily read the request body as JSON using the following code and parse it to GraphQLRequest,

var request = await JsonSerializer
                    .DeserializeAsync<GraphQLRequest>(
                        context.Request.Body,
                        new JsonSerializerOptions
                        {
                            PropertyNameCaseInsensitive = true
                        });

We are following the code-first approach for creating a schema. So, we have a Query class as follows,

public class GameStoreQuery : ObjectGraphType
{
    public GameStoreQuery()
    {
        Field<StringGraphType>(
            name: "name",
            resolve: context => "Steam"
        );
    }
}
GameStoreQuery.cs

Finally, we use DocumentExecuter to execute the query. It returns a ExecutionResult. We use the DocumentWriter to write the ExecutionResult to the context response body.

var schema = new Schema { Query = new GameStoreQuery() };

var result = await new DocumentExecuter()
                .ExecuteAsync(doc =>
                {
                    doc.Schema = schema;
                    doc.Query = request.Query;
                }).ConfigureAwait(false);

await new DocumentWriter(indent: true).WriteAsync(context.Response.Body, result);
Startup.cs
  • DocumentExecuter is available in GraphQL namespace
  • DocumentWriter is available in GraphQL.NewtonsoftJson namespace

So, after all the modifications, code in the UseEndpoints method of Startup.cs looks as follows,

app.UseEndpoints(endpoints =>
{
    endpoints.MapPost("/graphql", async context =>
    {

        var request = await JsonSerializer
                            .DeserializeAsync<GraphQLRequest>(
                                context.Request.Body,
                                new JsonSerializerOptions
                                {
                                    PropertyNameCaseInsensitive = true
                                });

        var schema = new Schema { Query = new GameStoreQuery() };

        var result = await new DocumentExecuter()
                        .ExecuteAsync(doc =>
                        {
                            doc.Schema = schema;
                            doc.Query = request.Query;
                        }).ConfigureAwait(false);

        await new DocumentWriter(indent: true).WriteAsync(context.Response.Body, result);
    });
});
Startup.cs

Now you make a POST request containing the query field using any rest client (Postman/Insomnia),

Insomnia Rest Client

In Insomnia, you can send structured graphql query like we are used to from our previous console app,

query {
    name
}

We are pretty much done with this post. But you can see that we have a lot of newing ups of objects like the new DocumentExecuter(), new Schema(), new DocumentWriter(), etc. In the next post, we will see how we can use the built-in dependency system of ASP.NET Core and make them injectable.

Part - II: Middleware

fiyazbinhasan/GraphQLCoreFromScratch
https://fiyazhasan.me/tag/graphql/. Contribute to fiyazbinhasan/GraphQLCoreFromScratch development by creating an account on GitHub.

ASP.NET Core Middleware