2

I am new to GraphQL and I'm currently trying to build an api for a existing library.

There I have Tags which have an unique alias, a value which can be of different types and some properties.

To take care of the different value types, I have defined an interface containing alias and properties as well as 4 implementations with a differently typed value field.

The GraphQL schema is as follows:

schema {
  query: Query
  mutation: Mutation
}

type Query {
    allTags: [Tag!]
    tags(aliases: [ID!]!): [Tag]
    tag(alias: ID!): Tag
}

interface Tag {
    alias: ID! @isUnique
    properties: [TagProperty]
}

type StringTag implements Tag {
    alias: ID! @isUnique
    properties: [TagProperty]
    value: String
}

type IntegerTag implements Tag {
    alias: ID! @isUnique
    properties: [TagProperty]
    value: Int
}

type FloatTag implements Tag {
    alias: ID! @isUnique
    properties: [TagProperty]
    value: Float
}

type BooleanTag implements Tag {
    alias: ID! @isUnique
    properties: [TagProperty]
    value: Boolean
}

type TagProperty {
    name: String!
    value: String!
}

In the interface implementation in C#, I want to use ResolveType as suggested in the graphql-dotnet getting started docs.

The code for the interface implementation:

public TagInterface(
    BooleanTag booleanTag, 
    IntegerTag integerTag, 
    FloatTag floatTag, 
    StringTag stringTag)
{
    Name = "Tag";
    Description = "A resource which points to a value";

    Field<IdGraphType>(
        "alias",
        "the unique alias of the tag"
    );
    Field<ListGraphType<TagPropertyType>>(
        "properties",
        "the properties of a tag defined in the config XML"
    );

    ResolveType = obj =>
    {
        if (obj is HtTag)
        {
            switch ((obj as HtTag).DataType) {
                case EDataType.Bool:

                    return booleanTag;

                case EDataType.Byte:
                case EDataType.Sbyte:
                case EDataType.Short:
                case EDataType.Ushort:
                case EDataType.Int:
                case EDataType.Uint:
                case EDataType.Long:
                case EDataType.Ulong:
                    return integerTag;

                case EDataType.Double:
                case EDataType.Float:
                case EDataType.Decimal:
                    return floatTag;

                default:
                    return stringTag;
            }
        }

        throw new ArgumentOutOfRangeException($"Could not resolve graph type for {obj.GetType().Name}");
    };
}

}

When I run the following query

query {
    tags(aliases:["TagID_S1A02_IN","TagID_S1A03_IN"]) {
      __typename
      alias
      ... on StringTag {
        value
        }
      ... on IntegerTag {
        value
        }
      ... on BooleanTag {
        value
        }
      ... on FloatTag {
        value
        }
    }
}

I get the following error:

{
    "errors": [
        {
            "message": "Für dieses Objekt wurde kein parameterloser Konstruktor definiert."
        }
    ]
}

This seems to be because of the 4 constructor parameters in the interface definition (which is according to the example in the docs). If I remove them and return new StringTag() etc. in the ResolveType implementation, I get the correct number of results, but without content:

{
    "data": {
        "tags": [
            {
                "__typename": "StringTag",
                "alias": null,
                "value": null
            },
            {
                "__typename": "StringTag",
                "alias": null,
                "value": null
            }
        ]
    }
}

Can anyone please point out to me what I'm doing wrong here?

Update

I got it working by using the parameterless constructor and returning the resolved object like this:

ResolveType = obj =>
    {
        if (obj is HtTag)
        {
            switch ((obj as HtTag).DataType) {
                case EDataType.Bool:
                    return (from t in PossibleTypes where t is BooleanTag select t).First();

                case EDataType.Byte:
                case EDataType.Sbyte:
                case EDataType.Short:
                case EDataType.Ushort:
                case EDataType.Int:
                case EDataType.Uint:
                case EDataType.Long:
                case EDataType.Ulong:
                    return (from t in PossibleTypes where t is IntegerTag select t).First();

                case EDataType.Double:
                case EDataType.Float:
                case EDataType.Decimal:
                    return (from t in PossibleTypes where t is FloatTag select t).First();

                default:
                    return (from t in PossibleTypes where t is StringTag select t).First();
            }
        }

        throw new ArgumentOutOfRangeException($"Could not resolve graph type for {obj.GetType().Name}");
    };

Is this the way it is supposed to be used?

arose
  • 138
  • 1
  • 8

1 Answers1

1

To be able to resolve your TagInterface type you will need to use some form of Dependency Injection. Your TagInterface and all of the other GraphType schema types will need to be registered with your DI container.

If using the 2.0 pre-release, use the following (.NET Core example):

services.AddSingleton<ISchema>(
  s => new StarWarsSchema(new FuncDependencyResolver(type => (IGraphType)s.GetRequiredService(type))));

https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL.GraphiQLCore/Startup.cs#L29

Older versions you can just use a func directly (.NET Core example):

services.AddSingleton<ISchema>(
  s => new StarWarsSchema(type => (IGraphType)s.GetRequiredService(type)));

See these docs (not updated to the 2.0 alpha yet) for more info:

https://graphql-dotnet.github.io/docs/getting-started/dependency-injection

You may also need to register those Tag types using RegisterType on Schema if they are not publicly exposed somewhere else in your Schema.

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.

https://graphql-dotnet.github.io/docs/getting-started/interfaces#registertype

Pete Duncanson
  • 3,208
  • 2
  • 25
  • 35
Joe McBride
  • 3,789
  • 2
  • 34
  • 38
  • Hi Joe, thanks for you reply. I already register the interface implementations using `RegisterType<>()`. The types also get resolved correctly (see last code fragment in orig. post). When I set a breakpoint inside the alias resolve function of `StringTag`, the correct data is there too. But it does not return in the response! – arose Nov 17 '17 at 07:09
  • I am confused, because you would only hit the error you posted if it is trying to create a type with `Activator.CreateInstance`, which is the default implementation. You're saying that `TagInterface` was resolved, along with all of its constructor parameters? What DI container are you using? – Joe McBride Nov 17 '17 at 17:49
  • Played with this for a bit. You'll want to make sure you're passing the DI resolver to the base class `Schema` class. `TagSchema(IDependencyResolver resolver) : base(resolver)`. You also want to make sure your *Tag types are registered as Singletons, since the instance resolved in the `ctor` may not be the same instance that gets registered with the `Schema` lookup. That may be the two issues you ran into. – Joe McBride Nov 17 '17 at 19:12
  • Well, im currently not using any DI containers and I'm on plain .Net 4.5 without ASP.NET... And I also do not directly use a dependency resolver. – arose Nov 20 '17 at 12:10
  • The only way to use `ctor` injection is to use a DI container (which is not provided for you by graphql-dotnet), so that would be your problem. – Joe McBride Dec 12 '17 at 00:29
  • well, I don't get how I would use that with the .Net Framework (not ASP.NET or ASP.NET Core). As written in the edited question, I found a solution in returning the type from within the `PossibleTypes` collection. But I would really like to understand it! – arose Dec 13 '17 at 07:55