graphql-dotnet / graphql-client

A GraphQL Client for .NET Standard
MIT License
623 stars 134 forks source link

Get strong typed object from GraphQLHttpClient GraphQLResponse #331

Open ShvetsovAU opened 3 years ago

ShvetsovAU commented 3 years ago

Hello.

I am using GraphQLHttpClient library (https://github.com/graphql-dotnet/graphql-client.git) for query from one net.core servise to other. Other service use GraphQLServer library (https://github.com/graphql-dotnet/server).

Example types and queries is:

 public abstract class CodeBase
    {
        //[Key]
        [Column(Order = 1)]
        public int TypeObjectId { get; set; }

        //[Key]
        [Column(Order = 2)]
        public int ValueObjectId { get; set; }

        [MaxLength(100)]
        public string ValueName { get; set; }
    }

[Table("CodeActivity")]
    public partial class CodeActivity : CodeBase
    {
        //[Key]
        [Column(Order = 0)]
        public int ActivityObjectId { get; set; }

        [ForeignKey("ActivityObjectId")]
        [Required]
        public virtual Activity Activity { get; set; }

        [ForeignKey("ValueObjectId")]
        [Required]
        public virtual ActivityCode ActivityCode { get; set; }

        [ForeignKey("TypeObjectId")]
        [Required]
        public virtual ActivityCodeType ActivityCodeType { get; set; }

        [NotMapped]
        public bool IsNew { get; set; }
    }

public class CodeActivityGraphType : ObjectGraphType<CodeActivity>
    {
        public CodeActivityGraphType()
        {
            Name = "CodeActivity";
            Field(x => x.TypeObjectId);
            Field(x => x.ValueObjectId);
            Field(x => x.ValueName, nullable: true);

            Field(f => f.ActivityObjectId);
            Field(f => f.Activity, false, typeof(ActivityGraphType));//.Resolve(ResolveFrom);
            Field(f => f.ActivityCode, false, typeof(ActivityCodeGraphType));//.Resolve(ResolveFrom);
            Field(f => f.ActivityCodeType, false, typeof(ActivityCodeTypeGraphType));//.Resolve(ResolveFrom);

            Field(f => f.IsNew);
        }

        private Activity ResolveFrom(IResolveFieldContext<Activity> context)
        {
            var activity = context.Source;
            return activity;
        }
    }

public class DataSchemaQuery : ObjectGraphType
    {
        public DataSchemaQuery(IServiceProvider serviceProvider)
        {
            try
            {
                Field<ListGraphType<CodeActivityGraphType>>("codeActivityList", "Returns a list of CodeActivity",
                    new QueryArguments(
                        new QueryArgument<IntGraphType> {Name = "take"},
                        new QueryArgument<IntGraphType> {Name = "skip"}
                    ),

                    context =>
                    {
                        using var scope = serviceProvider.CreateScope();
                        var repository = scope.ServiceProvider.GetRequiredService<IRepository<CodeActivity>>();

                        return repository.Find(null, context.Arguments["take"].GetPropertyValue<int?>(),
                            context.Arguments["skip"]?.GetPropertyValue<int>() ?? 0).AsNoTracking().ToList();
                    });

                Field<CodeActivityGraphType>("codeActivity", "Returns a Single CodeActivity",
                    new QueryArguments(
                        new QueryArgument<NonNullGraphType<IntGraphType>>
                        {
                            Name = "activityObjectId",
                            Description = "Activity Id"
                        },
                        new QueryArgument<NonNullGraphType<IntGraphType>>
                        {
                            Name = "typeObjectId",
                            Description = "Activity Id"
                        }),

                    context =>
                    {
                        using var scope = serviceProvider.CreateScope();
                        var repository = scope.ServiceProvider.GetRequiredService<IRepository<CodeActivity>>();

                        return repository.Find(ca =>
                                ca.ActivityObjectId == context.Arguments["activityObjectId"].GetPropertyValue<int>()
                                && ca.TypeObjectId == context.Arguments["typeObjectId"].GetPropertyValue<int>())
                            .FirstOrDefault();
                    });

            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    }
}

On client service code next:

var dataServiceUrl = "http://localhost:5002/graphql";
using var graphQLClient = new GraphQLHttpClient(dataServiceUrl, new SystemTextJsonSerializer()); 
            var codeActivities = new GraphQLRequest
            {
                Query = @"              
                    query ($activityObjectId: Int!, $typeObjectId: Int!) { 
                        codeActivity(activityObjectId: $activityObjectId, typeObjectId: $typeObjectId) {
                            activityObjectId
                            typeObjectId
                            valueObjectId
                            valueName                       
                    }
                }",
                Variables = new
                {
                    activityObjectId = 45685563,
                    typeObjectId = 97842
                }
            };

            var graphQLResponse = await graphQLClient.SendQueryAsync<CodeActivity>(codeActivities);

in in graphQLResponse.Data i get new CodeActivity entity with empty fileds and properties. How i can get strong type entity CodeActivity with data wich returned server side?

Server side return next:

' { "Data": { "codeActivity": { "activityObjectId": 45685563, "typeObjectId": 97842, "valueObjectId": 1292036, "valueName": "3 (\u0425\u0413\u041E)" } }, "Errors": null, "Extensions": { "tracing": { "version": 1, "startTime": "2021-03-10T06:33:36.742064Z", "endTime": "2021-03-10T06:33:39.652064Z", "duration": 2909844400, "parsing": {...}, "validation": { ...}, "execution": { "resolvers": [ {...} ] } } } } '

Other question.

If i want get list of CodeActivity i wrote next code:

var dataServiceUrl = "http://localhost:5002/graphql";
using var graphQLClient = new GraphQLHttpClient(dataServiceUrl, new SystemTextJsonSerializer());

            var codeActivities = new GraphQLRequest
            {
                Query = @"              
                    query ($take: Int, $skip: Int) { 
                        codeActivityList(take: $take, skip: $skip) {
                            activityObjectId
                            typeObjectId
                            valueObjectId
                            valueName                       
                    }
                }",
                Variables = new
                {
                    take = 100, //take = null,
                    skip = 0
                }
            };

var graphQLResponse = await graphQLClient.SendQueryAsync<ListGraphType<CodeActivityGraphType>>(codeActivities);

I get error : " Serialization and deserialization of 'System.Type' instances are not supported and should be avoided since they can lead to security issues. Path: $.Data.Type. ---> System.NotSupportedException: Serialization and deserialization of 'System.Type' instances are not supported and should be avoided since they can lead to security issues. at System.Text.Json.Serialization.Converters.TypeConverter.Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options) at System.Text.Json.Serialization.JsonConverter1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.JsonPropertyInfo1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer) at System.Text.Json.Serialization.Converters.ObjectDefaultConverter1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.JsonConverter1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.JsonPropertyInfo1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer) at System.Text.Json.Serialization.Converters.ObjectDefaultConverter1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.JsonConverter1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.JsonConverter1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state) --- End of inner exception stack trace --- at System.Text.Json.ThrowHelper.ThrowNotSupportedException(WriteStack& state, NotSupportedException ex) at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.JsonSerializer.WriteCore[TValue](JsonConverter jsonConverter, Utf8JsonWriter writer, TValue& value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.JsonSerializer.WriteCore[TValue](Utf8JsonWriter writer, TValue& value, Type inputType, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.Serialize[TValue](TValue& value, Type inputType, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options) at ASE.MD.MDP2.Product.MDP2Service.Controllers.GraphQLController.GetCodeActivitiesByGraphQL() in D:\WorkProjects\MDP\2.0\MDP2.0\MDP2Service\Controllers\GraphQLController.cs:line 130 at lambda_method231(Closure , Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gLogged|12_1(ControllerActionInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gLogged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context) --"

if i replace var graphQLResponse = await graphQLClient.SendQueryAsync<ListGraphType>(codeActivities) on

var graphQLResponse = await graphQLClient.SendQueryAsync<IEnumerable<CodeActivityGraphType>>(codeActivities);

get error

"System.Text.Json.JsonException: The JSON value could not be converted to System.Collections.Generic.IEnumerable1[ASE.MD.MDP2.Product.MDP2Service.DAL.GraphQL.Types.CodeActivityGraphType]. Path: $.data | LineNumber: 0 | BytePositionInLine: 9. at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType) at System.Text.Json.Serialization.Converters.IEnumerableDefaultConverter2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value) at System.Text.Json.Serialization.JsonConverter1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value) at System.Text.Json.JsonPropertyInfo1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader) at System.Text.Json.Serialization.Converters.ObjectDefaultConverter1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value) at System.Text.Json.Serialization.JsonConverter1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value) at System.Text.Json.Serialization.JsonConverter1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.JsonSerializer.ReadCore[TValue](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.JsonSerializer.ReadCore[TValue](JsonReaderState& readerState, Boolean isFinalBlock, ReadOnlySpan1 buffer, JsonSerializerOptions options, ReadStack& state, JsonConverter converterBase) at System.Text.Json.JsonSerializer.ReadAsync[TValue](Stream utf8Json, Type returnType, JsonSerializerOptions options, CancellationToken cancellationToken) at GraphQL.Client.Http.GraphQLHttpClient.SendHttpRequestAsync[TResponse](GraphQLRequest request, CancellationToken cancellationToken) at GraphQL.Client.Http.GraphQLHttpClient.SendQueryAsync[TResponse](GraphQLRequest request, CancellationToken cancellationToken) at ASE.MD.MDP2.Product.MDP2Service.Controllers.GraphQLController.GetCodeActivitiesByGraphQL() in D:\WorkProjects\MDP\2.0\MDP2.0\MDP2Service\Controllers\GraphQLController.cs:line 129 at lambda_method231(Closure , Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gLogged|12_1(ControllerActionInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gLogged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)"

Server side return: ' { "Data": { "codeActivityList": [ { "activityObjectId": 3338256, "typeObjectId": 206, "valueObjectId": 3177, "valueName": "ТЕХНч" }, { "activityObjectId": 3338256, "typeObjectId": 207, "valueObjectId": 3702, "valueName": "10UKC" }, { "activityObjectId": 3338256, "typeObjectId": 460, "valueObjectId": 3645, "valueName": "Арматура" }, { "activityObjectId": 3338256, "typeObjectId": 461, "valueObjectId": 3724, "valueName": "2" }, { "activityObjectId": 3338256, "typeObjectId": 721, "valueObjectId": 3711, "valueName": "шт" } ] }, "Errors": null, "Extensions": { "tracing": { "version": 1, "startTime": "2021-03-10T06:33:36.742064Z", "endTime": "2021-03-10T06:33:39.652064Z", "duration": 2909844400, "parsing": {...}, "validation": { ...}, "execution": { "resolvers": [ {...} ] } } } } '

what's wrong in queries? How i can get List<> of strong type entity CodeActivity?

sungam3r commented 3 years ago

Regarding second question. You use System.Text.Json.JsonSerializer.Serialize[TValue](TValue value) in your code. What type the value is? GraphQL.NET has special converters to serialize ExecutionResult. See GraphQL.SystemTextJson source code in this repo. I transfer this question in graphql-client repo, as it concerns the client and not the server.

ShvetsovAU commented 3 years ago

Regarding second question. You use System.Text.Json.JsonSerializer.Serialize[TValue](TValue value) in your code. What type the value is? GraphQL.NET has special converters to serialize ExecutionResult. See GraphQL.SystemTextJson source code in this repo.

As i understand type is GraphQLRequest

GraphQL.NET has special converters to serialize ExecutionResult

If i use code as in GraphQL.Server.Example

            services               
                .AddGraphQL((options, provider) =>
                {
                    options.EnableMetrics = Environment.IsDevelopment();
                    var logger = provider.GetRequiredService<ILogger<Startup>>();
                    options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occured", ctx.OriginalException.Message);
                })
#if NETCOREAPP2_2
                .AddNewtonsoftJson(deserializerSettings => { }, serializerSettings => { })
#else
                .AddSystemTextJson(deserializerSettings => { }, serializerSettings => { })
#endif

What Serializer i must use on client side?

rose-a commented 3 years ago

You're missing the root object of your query (one with a property codeActivityList)...

The easiest way would be to use the extension method which allows to declare an anonymous response type:

var graphQLResponse = await graphQLClient.SendQueryAsync(codeActivities, () => new {
  codeActivityList = new List<CodeActivity>()
});

Don't use the graph type from the server, you need the actual object! It should not matter which serializer you use, although I'd recommend using the same one on server and client side if possible to avoid serialization issues in "edge cases".