Using GraphQL in existing ASP.NET Web API projects (2)

In our last article, we built a simple GraphQL Web API backend which had action methods that accepted strings which contain GraphQL queries.

See sample here:

[HttpPost]
[Route("~/api/graph")]
public async Task<IHttpActionResult> Post([FromBody] string query)  
{
    var result = await new DocumentExecuter().ExecuteAsync((options) =>
    {
        options.Schema = new Schema() { Query = new BooksQuery() };
        options.Query = query;
    });
    _checkForErrors(result);
    var json = new DocumentWriter(indent: true).Write(result);
    return Ok(JObject.Parse(json));
}

In this article, we'd be showing you something slightly different.

Now, you flick your wrist this way ... Oops!

Strongly-Typed ASP.NET Web API GraphQL Endpoints

One of the beauties of .NET however is strong-typing, which is working with concrete objects that have concrete definitions.

We'd be missing out on this if all our GraphQL endpoints accepted string arguments.

I hereby propose this fix to help with that.

I have created a graphql-typed branch in our repository for this article.

How it'll work

Attributes

We can create an Attribute (or Decorator, if you roll that way), that accepts the Query Type we expect the Action to receive as an argument.

Example:

[HttpPost]
[GraphType(typeof(BooksQuery))]
public IHttpActionResult Post([FromBody] ExecutionResult result)  
{
    var json = new DocumentWriter(indent: true).Write(result);
    return Ok(JObject.Parse(json));
}

In the example above, the Post method accepts an ExecutionResult argument which is the equivalent of a GraphQL query string object called query being processed using the following code:

await new DocumentExecuter().ExecuteAsync((options) =>  
{
    options.Schema = new Schema() { Query = new BooksQuery() };
    options.Query = query;
});

The DocumentExecuter class uses the BooksQuery type to generate a schema which it uses to convert the query string into an ExecutionResult instance, which can then be processed by GraphQL to retrieve data.

All this is made possible by the GraphType attribute.

Let's see how the Attribute works, eh?

About the GraphType Attribute

public class GraphTypeAttribute : FilterAttribute, IActionFilter  
{
   private readonly string _parameterName;
   private readonly Type _parameterType;
   public GraphTypeAttribute(Type type, string parameterName = null)
   {
       this._parameterName = parameterName;
       this._parameterType = type;
   }

   public async Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
   {
      string query = Convert.ToString(actionContext.ActionArguments.ToArray().First().Value);
      var result = await new DocumentExecuter().ExecuteAsync((options) =>
      {
         options.Schema = new Schema() { Query = (IObjectGraphType)Activator.CreateInstance(_parameterType) };
         options.Query = query;
      });
      actionContext.ActionArguments[_parameterName ?? actionContext.ActionArguments.ToArray().First().Key] = result;
      return await continuation();
   }
}

I'll explain:

The GraphTypeAttribute class extends FilterAttribute and implements IActionFilter which means it acts as an Action Filter and should be used on Action Methods, and it accepts the Type of Query that you expect the Action to process.

In case your Action has multiple parameters, it may also accept the parameterName string in its constructor which represents the name of the parameter that the GraphQL request should resolve to.

Just before your Action method is called to be executed, the ExecuteActionFilterAsync method will be run on the call stack. It will process the HttpContext by getting the graphql query string, converting it to an ExecutionResult instance using the Type passed in to the GraphType attribute constructor, and point the reference to that string, to the ExecutionResult instance instead.

The request pipeline is then continued with the await continuation() call.

Phew!

Wanna see some code!

Let's try this on our repository.

See our GraphTypeAttribute.cs class:

Note that the _checkForErrors method has been moved from the controller to the GraphTypeAttribute class, which means that whatever argument data our Post Action method receives is guaranteed to be valid and error-free.

See our new GraphController.cs file:

[HttpPost]
[Route("~/api/graph")]
[GraphType(typeof(BooksQuery))]
public async Task<IHttpActionResult> Post([FromBody] ExecutionResult result)  
{
   var json = new DocumentWriter(indent: true).Write(result);
   return Ok(JObject.Parse(json));
}

What does this mean for code?

  • C# is a Strongly-Typed Language, so with this, we are playing to its strengths.
  • We are able to detect errors in our requests earlier and we can predict what data an endpoint will receive.
  • With our error-handling within the GraphTypeAttribute class, we can be sure that the ExecutionResult instance passed to our controller action will be completely free of errors.

Hopefully, we'll see more and more developers adopt GraphQL in their Web API projects now that it's easy to use.

Give your questions in the comments section below and don't forget to recommend to your friends if you like this article.

Show Comments