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.

Fields

We already have a good idea of GraphQL Fields. Remember we have a field under the GameStoreQuery i.e. name,

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

It's a simple scaler field. However, we can work with complex types as well; as the official documentation states,

"At its simplest, GraphQL is about asking for specific fields on objects" - graphql.org

Let's extend our simple application to accommodate a complex type. Say, for example, we are heading towards a path of making a Digital Distribution Service (DDS) platform like Steam or GOG. Start by creating an Item type,

public class Item
{
	public string Tag {get; set; }
    public string Title { get; set; }
    public decimal Price { get; set; }
}
Item.cs

However, we can't directly query against this object as it is not a GraphQL object i.e. not an ObjectGraphType. To make it GraphQL queryable, we should create a new type and extend it from ObjectGraphType.

Another flavour of ObjectGraphType takes generic types. As you already guessed it, we will pass the Item type as its generic argument.

public class ItemType : ObjectGraphType<Item>
{
    public ItemType()
    {
    	Field(i => i.Tag);
        Field(i => i.Title);
        Field(i => i.Price);
    }
}
ItemType.cs

Two things to notice down here. First, we no longer have type declarations in the fields. They will automatically get resolved by the library i.e. dot net string type will be resolved to StringGraphType. Second, we used lambda expressions to map properties and resolve things like the name of the fields. This concept of property matching should be easy to understand for the people who are familiar with the notion of DTOs/ViewModels. So, whoever thinks that we are dealing with an extra burden of type creation; trust me, we are not!

Next, we need to register the ItemType in our root query object i.e. GameStoreQuery,

public GameStoreQuery()
{
    Field<StringGraphType>(
        name: "name",
        resolve: context => "Steam"
    );

    Field<ItemType>(
        "item",
        resolve: context =>
        {
            return new Item
            {
            	Tag = "cyberpunk_2077"
                Title = "Cyberpunk 2077",
                Price = 59.99M
            };
        }
    );
}
GameStoreQuery.cs

For the time being, we are returning a hard-coded instance of Item when someone tries to query the item field. Finally we have to wire up this new type with the DI container,

public void ConfigureServices(IServiceCollection services)
{
    /* Code removed for brevity */ 
    
    services.AddTransient<ItemType>();

    /* Code removed for brevity */ 
}
Startup.cs

We can run our application now and do the following,

Arguments

Serving a hard-coded instance is not going to cut it. How about we introduce a data source which will serve the purpose of giving away a list of items,

public class DataSource
{
	public IList<Item> Items { get; set; }

	public DataSource()
	{
		Items = new List<Item>(){
			new Item { Tag= "cyberpunk_2077", Title="Cyberpunk 2077", Price=59.99M },
			new Item { Tag= "disco_elysium", Title="Disco Elysium", Price= 39.99M },
			new Item { Tag= "diablo", Title="Diablo + Hellfire", Price= 9.99M }
		};
	}

	public Item GetItemByTag(string tag)
	{
		return Items.First(i => i.Tag.Equals(tag));
	}
}
DataSource.cs

Along with the Items collection, we also have a method which returns a single item that matches a passed in tag string.

Great! Now to pass in an argument via the query we have to modify the item field as followings,

Field<ItemType>(
    "item",
    arguments: new QueryArguments(new QueryArgument<StringGraphType> { Name = "tag" }),
    resolve: context =>
    {
        var tag = context.GetArgument<string>("tag");
        return new DataSource().GetItemByTag(tag);
    }
);
GameStoreQuery.cs

There could be a list of arguments; some required and some optional. We specify an individual argument and its type with the QueryArgument<T>. The Name represents the name of an individual argument.

Now, we can construct a query inside GraphiQL with pre-defined arguments as followings,

query GameByTagQuery {
  item(tag: "cyberpunk_2077") {
    title
    price
  }
}

At this point, the barcode argument is optional. So, if you do something like below,

query {
  item {
    title
    price
  }
}

It will throw an error saying Error trying to resolve item.

That's fair since we didn't really write our code in a safe way. To ensure that user always provides an argument we can make the argument non-nullable with the following syntax,

QueryArgument<NonNullGraphType<StringGraphType>> { Name = "tag" }

So, if we try to execute the same query in GraphiQL, it will give you a nice error message as followings,

Variables

It's time to make the argument itself dynamic. We don't want to construct a whole query whenever we want to change a value of an argument, do we? Hence come variables. But first, we have to make sure our GraphQL middleware accepts variables. Go back to the GraphQLRequest class and add a Variables property.

using System.Collections.Generic;
using System.Text.Json.Serialization;
using GraphQL.SystemTextJson;

namespace Web.GraphQL
{
    public class GraphQLRequest
    {
        public string Query { get; set; }

        [JsonConverter(typeof(ObjectDictionaryConverter))]
        public Dictionary<string, object> Variables
        {
            get; set;
        }
    }
}
GraphQLRequest.cs

Notice, we have a new import GraphQL.SystemTextJson. Install this package from the GraphQL Dotnet personal package source,

dotnet add package GraphQL.SystemTextJson -v 3.0.0-preview-1650 -s https://www.myget.org/F/graphql-dotnet/api/v3/index.json

Next find out the _executor.ExecuteAsync method in the middleware's InvokeAsync method and modify as followings,

var result = await _executor.ExecuteAsync(doc =>
{
    doc.Schema = _schema;
    doc.Query = request.Query;
    doc.Inputs = request.Variables.ToInputs();
}).ConfigureAwait(false);

Nice! our query is now ready to accept variables. Run the application and write a query as followings,

query($tag: String!){
  item(tag: $tag){
    title
    price
  }
}

Variable definition starts with a $ sign, followed by the variable type. Since we made the barcode argument non-nullable, here we also have to make sure the variable is non-nullable. Hence we used a ! mark after the String type.

To use the variable to transfer data we have a Query Variables pane inside GraphiQL. Use it to configure required variables as followings,

Part V

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

Create Data Transfer Objects (DTOs)

Mapping Entity Framework Entities to DTOs with AutoMapper

GraphQL Fields

GraphQL Arguments

GraphQL Variables