Using GraphQL in existing ASP.NET Web API projects

GraphQL is cool!

GraphQL is an awesome something

It is an awesome something

In fact, it should be named GraphCool, but that would sound like the name of really nerdy drink that reminds you of Math everytime you sip it.

Wink

Hey, we'd still drink it, right?

This article is for ASP.NET developers who work with Web APIs. We will go straight to discussing ASP.NET, so for a quick introduction to what GraphQL is, please see GraphQL: New, shiny and worth the hype! by @timigod. It also gives excellent reasons for why GraphQL is important.

A simple example of a GraphQL query is:

{
  person (personID: 1){
    name,
    birthYear,
    eyeColor,
    gender
  }
}

See it in action on this API explorer.

That simply says, return the name, birthYear, eyeColor and gender of the person with personId value of 1.

GraphQL, I am your father - LINQ

The query above looks a bit like JSON, but it isn't. For instance, it doesn't have the double-quotes that are JSON's trademark, and it seems to have something like a constructor.

How do we make sense of that?

Luckily, there are packages in Nuget that absolutely help make it easy.

We have created a starter WebAPI project for this article on GitHub, which you can fork, and that already contains references to:

The project gives information on books and their authors. Umm, don't pay much attention to the information. It's randomly generated, anyway.

When you start the application, you can run the endpoints defined in the README to see the type of data available on the API.

We can already see that GraphQL syntax is a bit different from what we're used to, but I should also point out that GraphQL queries are typically sent via POST request, and with the application/graphql Content-Type header.

ASP.NET WebAPI projects are typically rigged with default formatters to handle requests with application/json and text/xml Content-Type headers, so we will have to create a custom formatter for GraphQL queries.

Let's call it GraphQLFormatter:

public class GraphQLFormatter : BufferedMediaTypeFormatter  
    {
        public GraphQLFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/graphql"));
        }

        public override bool CanReadType(Type type)
        {
            return type == typeof(string) || type == typeof(ExecutionResult) || type is IObjectGraphType;
        }

        public override bool CanWriteType(Type type)
        {
            return type == typeof(string) || type == typeof(ExecutionResult) || type is IObjectGraphType;
        }

        public override object ReadFromStream(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
        {
            StreamReader reader = new StreamReader(readStream);
            string str = reader.ReadToEnd();
            return str;
        }
    }

See full code.

A custom formatter in ASP.NET lets you define how ASP.NET treats requests with Content-Type headers it does not recognize.

We still need to register this with ASP.NET, so in our WebApiConfig.cs class, add:

config.Formatters.Add(new GraphQLWebAPI.Formatters.GraphQLFormatter());  

to the Register method;

Now, ASP.NET knows how to handle application/graphql Content-Types. Isn't that cool?

Next, we need to do some actual GraphQL stuff before you start getting bored. We'll tell GraphQL how to access our data by creating a BooksQuery class.

public class BooksQuery : ObjectGraphType  
    {
        public BooksQuery()
        {
            IBookRepository bookRepository = new BookRepository();
            Field<BookType>("book", arguments: new QueryArguments(
                  new QueryArgument<StringGraphType>() { Name = "isbn" }),
                  resolve: context =>
                  {
                      var id = context.GetArgument<string>("isbn");
                      return bookRepository.BookByIsbn(id);
                  });
            Field<ListGraphType<BookType>>("books",
                  resolve: context =>
                  {
                      return bookRepository.AllBooks();
                  });
        }
    }

    public class BookType : ObjectGraphType<BookModel>
    {
        public BookType()
        {
            Field(x => x.Isbn).Description("The isbn of the book.");
            Field(x => x.Name).Description("The name of the book.");
            Field<AuthorType>("author");
        }
    }

    public class AuthorType : ObjectGraphType<AuthorModel>
    {
        public AuthorType()
        {
            Field(x => x.Id).Description("The id of the author.");
            Field(x => x.Name).Description("The name of the author.");
            Field<BookType>("books");
        }
    }

In the class's constructor, we define two GraphQL fields. The first is "book" which takes a string argument isbn and queries a particular book via bookRepository.BookByIsbn(isbn). The second is books which queries the list of books present in our BookRepository.

BookType and AuthorType are GraphQL abstractions around BookModel and AuthorModel. They help GraphQL make sense of the data model it is supposed to work with.

Next, we add our GraphController class:

public class GraphController: ApiController  
    {
        [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));
        }

        private void CheckForErrors(ExecutionResult result)
        {
            if (result.Errors?.Count > 0)
            {
                var errors = new List<Exception>();
                foreach (var error in result.Errors)
                {
                    var ex = new Exception(error.Message);
                    if (error.InnerException != null)
                    {
                        ex = new Exception(error.Message, error.InnerException);
                    }
                    errors.Add(ex);
                }
                throw new AggregateException(errors);
            }
        }
    }

Our Post method accepts a string parameter called query which will contain our graphql query passed in the POST request body:

{
  books{
    isbn,
    name,
    author{
      id,
      name
    }
  }
}

Our query is used to create a GraphQL document along with a schema that is defined by the BooksQuery type, so GraphQL knows exactly where to run the query against.

This returns an ExecutionResult object which may contain errors or result values. We check for these with the CheckErrors method.

If valid, we convert the ExecutionResult object to a JSON string, and return to the client.

We can start our application now. I'll be using Postman, but you can use any client of your choice for creating your HTTP request.

Be sure to set the Content-Type header value to application/graphql and the request type to POST.

In the body, place:

{
  books{
    isbn,
    name,
    author{
      id,
      name
    }
  }
}

Set the url to http://localhost:3356/api/graph where http://localhost:3356/ is the root of my application (it may be different in yours, so be sure to change it accordingly).

SEND the request, and if all goes well, you should receive data similar to:

{
    "data": {
        "books": [
            {
                "isbn": "c4086602-8770-499b-a951-cc80a0e77f80",
                "name": "The law suit",
                "author": {
                    "id": 1,
                    "name": "David James"
                }
            },
            {
                "isbn": "4feaba82-3468-4981-8e65-235c9de98fea",
                "name": "The Cryptic Message",
                "author": {
                    "id": 2,
                    "name": "Peter Peter"
                }
            },
            {
                "isbn": "27492d61-ff00-458f-bf2e-f9ca715c6efb",
                "name": "A tale of two cities",
                "author": {
                    "id": 3,
                    "name": "Paul John"
                }
            } ...
}

If it worked, congratulations! You have just added GraphQL to an existing ASP.NET Web API project.

Try playing around with the graphql query and see how it affects the output.

If it didn't work, please holla in the comments section and we'll be glad to help.

One of the beauties of GraphQL is that whether your application stores its data in primary memory like ours, or in a data, GraphQL doesn't mind. It will work for them all.

We hope to see more ASP.NET developers adopting GraphQL in their applications in the future.

See what we've built in this git branch.

Happy Coding!

PS:

  • Our POST action in GraphController accepts a string parameter which contains a graphql query.

  • What if we could make accept a complex type that represents an actual query?

  • This would mean we get benefits of strong-typing, and we won't need the CheckForErrors method in every controller, yea?

  • In our next article, we'll have found out a way (or ways) to achieve this.

Show Comments