Interfaces
A GraphQL Interface is an abstract type that includes a certain set of fields that a type must include to implement the interface.
Here is an interface that represents a Character
in the StarWars universe.
interface Character {
id: ID!
name: String!
friends: [Character]
}
public class CharacterInterface : InterfaceGraphType<StarWarsCharacter>
{
public CharacterInterface()
{
Name = "Character";
Field(d => d.Id).Description("The id of the character.");
Field(d => d.Name).Description("The name of the character.");
Field<ListGraphType<CharacterInterface>>("friends");
}
}
Any type that implements Character
needs to have these exact fields, arguments, and return types.
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
primaryFunction: String
}
public class DroidType : ObjectGraphType<Droid>
{
public DroidType(IStarWarsData data)
{
Name = "Droid";
Description = "A mechanical creature in the Star Wars universe.";
Field(d => d.Id).Description("The id of the droid.");
Field(d => d.Name).Description("The name of the droid.");
Field<ListGraphType<CharacterInterface>>("friends").Resolve(context => data.GetFriends(context.Source));
Field(d => d.PrimaryFunction, nullable: true).Description("The primary function of the droid.");
Interface<CharacterInterface>();
}
}
RegisterType
When the Schema is built, it looks at the "root" types (Query, Mutation, Subscription) and
gathers all of the GraphTypes they expose. Often when you are working with an interface type
the concrete types are not exposed on the root types (or any of their children). Since those
concrete types are never exposed in the type graph the Schema doesn't know they exist. This
is what the RegisterType<>
method on the Schema is for. By using RegisterType<>
, it
tells the Schema about the specific type and it will properly add it to the PossibleTypes
collection on the interface type when the Schema is initialized.
public class StarWarsSchema : Schema
{
public StarWarsSchema()
{
Query = new StarWarsQuery();
RegisterType<DroidType>();
}
}
IsTypeOf
IsTypeOf
is a function which helps resolve the implementing GraphQL type during execution.
For example, when you have a field that returns a GraphQL Interface the engine needs to know
which concrete Graph Type to use. So if you have a Character
interface that is implemented
by both Human
and Droid
types, the engine needs to know which graph type to choose.
The data object being mapped is passed to the IsTypeOf
function which should return a boolean value.
public class DroidType : ObjectGraphType
{
public DroidType(IStarWarsData data)
{
Name = "Droid";
...
Interface<CharacterInterface>();
IsTypeOf = obj => obj is Droid;
}
}
ObjectGraphType<T>
provides a default implementation ofIsTypeOf
for you.
An alternate to using IsTypeOf
is instead implementing ResolveType
on the Interface
or Union. See the ResolveType
section for more details.
ResolveType
An alternate to using IsTypeOf
is implementing ResolveType
on the Interface or Union.
The major difference is ResolveType
is required to be exhaustive. If you add another type
that implements an Interface you are required to alter the Interface for that new type to be resolved.
If a type implements
ResolveType
then anyIsTypeOf
implementation is ignored.
public class CharacterInterface : InterfaceGraphType<StarWarsCharacter>
{
public CharacterInterface()
{
Name = "Character";
...
// Note: be sure not to pull in these references from DI when the graph types
// are registered as transients (the default lifetime for graph types)
var droidType = new GraphQLTypeReference("Droid");
var humanType = new GraphQLTypeReference("Human");
ResolveType = obj =>
{
if (obj is Droid)
{
return droidType;
}
if (obj is Human)
{
return humanType;
}
throw new ArgumentOutOfRangeException($"Could not resolve graph type for {obj.GetType().Name}");
};
}
}