Search code examples
c#.net-coregraphql-dotnet

graphql-dotnet how to call a query from code


Im currently trying to call a field on graphql-query from code, without using the http layer. In a test case I had success using this snippet inside of a field resolver. The breakpoint hits.

var newContext = new ResolveFieldContext(context);
var query = context.ParentType;
var ticketQueryField = query.GetField("getTickets");
await (Task) ticketQueryField.Resolver.Resolve(context);

So I think its possible to fill the copied ResolveFieldContext with my real needed fields/arguments and call it like this. But its very ... complicated to fill the ResolveFieldContext by hand. So maybe there is a easier way to create the context. Like:

var newContext = new ResolveFieldContext("query test { getTickets(id: 1) { number, title } }");

That would be really awesome and in my real scenario there a more then just field which I want to access with the generated query.

Why I want to use the Graph like this? The Batch-Loader which we are using inside the GraphQL-Types are perfect for our needs.


Solution

  • You can execute a GraphQL query without http by using the DocumentExecutor directly, and providing your own DocumentWriter if you want the data in a specific format. There is an extension method which returns JSON, but you can write your own.

    https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL.NewtonsoftJson/DocumentWriter.cs

    This is an example test base class for testing queries: https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL.Tests/BasicQueryTestBase.cs

    This is a console example that returns JSON, not using http.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;
    using GraphQL;
    using GraphQL.Authorization;
    using GraphQL.SystemTextJson;
    using GraphQL.Types;
    using GraphQL.Validation;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace BasicSample
    {
        internal class Program
        {
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "main")]
            private static async Task Main()
            {
                using var serviceProvider = new ServiceCollection()
                    .AddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>()
                    .AddTransient<IValidationRule, AuthorizationValidationRule>()
                    .AddTransient(s =>
                    {
                        var authSettings = new AuthorizationSettings();
                        authSettings.AddPolicy("AdminPolicy", p => p.RequireClaim("role", "Admin"));
                        return authSettings;
                    })
                    .BuildServiceProvider();
    
                string definitions = @"
                    type User {
                        id: ID
                        name: String
                    }
    
                    type Query {
                        viewer: User
                        users: [User]
                    }
                ";
                var schema = Schema.For(definitions, builder => builder.Types.Include<Query>());
    
                // remove claims to see the failure
                var authorizedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("role", "Admin") }));
    
                string json = await schema.ExecuteAsync(_ =>
                {
                    _.Query = "{ viewer { id name } }";
                    _.ValidationRules = serviceProvider
                        .GetServices<IValidationRule>()
                        .Concat(DocumentValidator.CoreRules);
                    _.RequestServices = serviceProvider;
                    _.UserContext = new GraphQLUserContext { User = authorizedUser };
                });
    
                Console.WriteLine(json);
            }
        }
    
        /// <summary>
        /// Custom context class that implements <see cref="IProvideClaimsPrincipal"/>.
        /// </summary>
        public class GraphQLUserContext : Dictionary<string, object>, IProvideClaimsPrincipal
        {
            /// <inheritdoc />
            public ClaimsPrincipal User { get; set; }
        }
    
        /// <summary>
        /// CLR type to map to the 'Query' graph type.
        /// </summary>
        public class Query
        {
            /// <summary>
            /// Resolver for 'Query.viewer' field.
            /// </summary>
            [GraphQLAuthorize("AdminPolicy")]
            public User Viewer() => new User { Id = Guid.NewGuid().ToString(), Name = "Quinn" };
    
            /// <summary>
            /// Resolver for 'Query.users' field.
            /// </summary>
            public List<User> Users() => new List<User> { new User { Id = Guid.NewGuid().ToString(), Name = "Quinn" } };
        }
    
        /// <summary>
        /// CLR type to map to the 'User' graph type.
        /// </summary>
        public class User
        {
            /// <summary>
            /// Resolver for 'User.id' field. Just a simple property.
            /// </summary>
            public string Id { get; set; }
    
            /// <summary>
            /// Resolver for 'User.name' field. Just a simple property.
            /// </summary>
            public string Name { get; set; }
        }
    }