GraphQL with .NET Core (Part - II: Middleware)
Code samples used in this blog series have been updated to latest version of .NET Core (5.0.4) and GraphQL-Dotnet (4.2.0). Follow this link to get the updated samples.
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!");
});
});
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 => {});
});
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; }
}
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"
);
}
}
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);
DocumentExecuter
is available inGraphQL
namespaceDocumentWriter
is available inGraphQL.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);
});
});
Now you make a POST
request containing the query field using any rest client (Postman/Insomnia),
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.