From 738f448b4de2bb8e84c1973417c8a8b558afe2e6 Mon Sep 17 00:00:00 2001 From: Antoine Griffard Date: Wed, 7 Apr 2021 14:46:16 +0200 Subject: [PATCH 01/29] GraphQL 4 --- src/OrchardCore.Build/Dependencies.props | 4 +- .../OrchardCore.Apis.GraphQL.csproj | 5 + .../OrchardFieldNameConverter.cs | 25 ++--- .../RequestServicesDependencyResolver.cs | 52 ++++----- .../Services/SchemaService.cs | 3 +- .../OrchardCore.Apis.GraphQL/Startup.cs | 6 +- .../MaxNumberOfResultsValidationRule.cs | 91 +++++++++------- .../RequiresPermissionValidationRule.cs | 77 +++++++------ .../Fields/ObjectGraphTypeFieldProvider.cs | 2 +- .../GraphQL/Types/HtmlFieldQueryObjectType.cs | 3 +- .../GraphQL/LocalizationQueryObjectType.cs | 1 + .../Services/UserProfileClaimsProvider.cs | 1 + .../GraphQL/HtmlBodyQueryObjectType.cs | 3 +- .../GraphQL/LayerQueryObjectType.cs | 3 +- .../GraphQL/SiteLayersQuery.cs | 5 +- .../GraphQL/ListQueryObjectType.cs | 5 +- .../GraphQL/SiteCulturesQuery.cs | 5 +- .../GraphQL/LuceneQueryFieldTypeProvider.cs | 1 + .../GraphQL/MarkdownBodyQueryObjectType.cs | 3 +- .../GraphQL/MarkdownFieldQueryObjectType.cs | 3 +- .../GraphQL/MediaAssetObjectType.cs | 2 +- .../GraphQL/MediaAssetQuery.cs | 5 +- .../GraphQL/MediaFieldQueryObjectType.cs | 2 +- .../Sql/GraphQL/SqlQueryFieldTypeProvider.cs | 1 + .../FieldBuilderResolverExtensions.cs | 3 +- .../ResolveFieldContextExtensions.cs | 21 +++- .../GraphQLUserContext.cs | 3 +- ...chardCore.Apis.GraphQL.Abstractions.csproj | 1 + .../Resolvers/LockedAsyncFieldResolver.cs | 19 ++-- .../Extensions/DataLoaderExtensions.cs | 4 +- .../Queries/ContentItemQuery.cs | 3 +- .../Queries/ContentItemsFieldType.cs | 27 +++-- .../Queries/GraphQLFilter.cs | 6 +- .../Queries/IGraphQLFilter.cs | 6 +- .../Queries/Types/ContentItemType.cs | 2 +- .../GraphQL/ContentItemsFieldTypeTests.cs | 103 ++++++++++-------- .../RequiresPermissionValidationRuleTests.cs | 8 +- 37 files changed, 287 insertions(+), 227 deletions(-) diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index f319ed18535..45ee9676a63 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -17,7 +17,9 @@ - + + + diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj index 7f96672786f..2980984fa04 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj @@ -14,6 +14,11 @@ + + + + + diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs index a6d1020d736..90a71e2ad9a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs @@ -1,28 +1,21 @@ using System; using GraphQL.Conversion; +using GraphQL.Types; namespace OrchardCore.Apis.GraphQL { - public class OrchardFieldNameConverter : IFieldNameConverter + public class OrchardFieldNameConverter : INameConverter { - private readonly IFieldNameConverter _defaultConverter = new CamelCaseFieldNameConverter(); + private readonly INameConverter _defaultConverter = new CamelCaseNameConverter(); - public string NameFor(string field, Type parentType) + public string NameForArgument(string fieldName, IComplexGraphType parentGraphType, FieldType field) { - var attributes = parentType?.GetCustomAttributes(typeof(GraphQLFieldNameAttribute), true); - - if (attributes != null) - { - foreach (GraphQLFieldNameAttribute attribute in attributes) - { - if (attribute.Field == field) - { - return attribute.Mapped; - } - } - } + return _defaultConverter.NameForArgument(fieldName, parentGraphType, field); + } - return _defaultConverter.NameFor(field, parentType); + public string NameForField(string fieldName, IComplexGraphType parentGraphType) + { + return _defaultConverter.NameForField(fieldName, parentGraphType); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/RequestServicesDependencyResolver.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/RequestServicesDependencyResolver.cs index c8f3fff28d7..fa94848263e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/RequestServicesDependencyResolver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/RequestServicesDependencyResolver.cs @@ -4,35 +4,35 @@ namespace OrchardCore.Apis.GraphQL { - /// - /// Provides an implementation of by - /// resolving the HttpContext request services when a type is resolved. - /// This should be registered as Singleton. - /// - internal class RequestServicesDependencyResolver : IDependencyResolver - { - private readonly IHttpContextAccessor _httpContextAccessor; + ///// + ///// Provides an implementation of by + ///// resolving the HttpContext request services when a type is resolved. + ///// This should be registered as Singleton. + ///// + //internal class RequestServicesDependencyResolver : IDependencyResolver + //{ + // private readonly IHttpContextAccessor _httpContextAccessor; - public RequestServicesDependencyResolver(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } + // public RequestServicesDependencyResolver(IHttpContextAccessor httpContextAccessor) + // { + // _httpContextAccessor = httpContextAccessor; + // } - public T Resolve() - { - return (T)Resolve(typeof(T)); - } + // public T Resolve() + // { + // return (T)Resolve(typeof(T)); + // } - public object Resolve(Type type) - { - var serviceType = _httpContextAccessor.HttpContext.RequestServices.GetService(type); + // public object Resolve(Type type) + // { + // var serviceType = _httpContextAccessor.HttpContext.RequestServices.GetService(type); - if (serviceType == null) - { - return Activator.CreateInstance(type); - } + // if (serviceType == null) + // { + // return Activator.CreateInstance(type); + // } - return serviceType; - } - } + // return serviceType; + // } + //} } diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs index 154db20b27e..09f9ad17bf8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs @@ -67,8 +67,7 @@ public async Task GetSchemaAsync() Query = new ObjectGraphType { Name = "Query" }, Mutation = new ObjectGraphType { Name = "Mutation" }, Subscription = new ObjectGraphType { Name = "Subscription" }, - FieldNameConverter = new OrchardFieldNameConverter(), - DependencyResolver = serviceProvider.GetService() + NameConverter = new OrchardFieldNameConverter() }; foreach (var builder in _schemaBuilders) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs index 0a6e2df772c..2cda9f2477b 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs @@ -2,7 +2,7 @@ using GraphQL; using GraphQL.DataLoader; using GraphQL.Execution; -using GraphQL.Http; +using GraphQL.NewtonsoftJson; using GraphQL.Validation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; @@ -35,7 +35,7 @@ public Startup(IOptions adminOptions, IHostEnvironment hostingEnvi public override void ConfigureServices(IServiceCollection services) { - services.AddSingleton(); + //services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -61,7 +61,7 @@ public override void ConfigureServices(IServiceCollection services) maxNumberOfResultsValidationMode = _hostingEnvironment.IsDevelopment() ? MaxNumberOfResultsValidationMode.Enabled : MaxNumberOfResultsValidationMode.Disabled; } - c.BuildUserContext = ctx => new GraphQLContext + c.BuildUserContext = ctx => new GraphQLUserContext { HttpContext = ctx, User = ctx.User, diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs index 3c5c0e8aba7..ecfc9153d2b 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using GraphQL.Language.AST; using GraphQL.Validation; using Microsoft.Extensions.DependencyInjection; @@ -19,53 +20,59 @@ public MaxNumberOfResultsValidationRule(IOptions options) _maxNumberOfResultsValidationMode = settings.MaxNumberOfResultsValidationMode; } - public INodeVisitor Validate(ValidationContext validationContext) + public Task ValidateAsync(ValidationContext validationContext) { - return new EnterLeaveListener(_ => - { - _.Match(arg => - { - if ((arg.Name == "first" || arg.Name == "last") && arg.Value != null) - { - var context = (GraphQLContext)validationContext.UserContext; + // Todo: EnterLeaveListener has been removed and the signatures of INodeVisitor.Enter and INodeVisitor.Leave have changed. NodeVisitors class has been added in its place. + // https://graphql-dotnet.github.io/docs/migrations/migration4/ + // Ex: https://github.com/graphql-dotnet/graphql-dotnet/issues/2406 + INodeVisitor result = new NodeVisitors(); + return Task.FromResult(result); - int? value = null; + //return new EnterLeaveListener(_ => + //{ + // _.Match(arg => + // { + // if ((arg.Name == "first" || arg.Name == "last") && arg.Value != null) + // { + // var context = (GraphQLUserContext)validationContext.UserContext; - if (arg.Value is IntValue) - { - value = ((IntValue)arg.Value)?.Value; - } - else - { - if (validationContext.Inputs.TryGetValue(arg.Value.ToString(), out var input)) - { - value = (int?)input; - } - } + // int? value = null; - if (value.HasValue && value > _maxNumberOfResults) - { - var localizer = context.ServiceProvider.GetService>(); - var errorMessage = localizer["'{0}' exceeds the maximum number of results for '{1}' ({2})", value.Value, arg.Name, _maxNumberOfResults]; + // if (arg.Value is IntValue) + // { + // value = ((IntValue)arg.Value)?.Value; + // } + // else + // { + // if (validationContext.Inputs.TryGetValue(arg.Value.ToString(), out var input)) + // { + // value = (int?)input; + // } + // } - if (_maxNumberOfResultsValidationMode == MaxNumberOfResultsValidationMode.Enabled) - { - validationContext.ReportError(new ValidationError( - validationContext.OriginalQuery, - "ArgumentInputError", - errorMessage, - arg)); - } - else - { - var logger = context.ServiceProvider.GetService>(); - logger.LogInformation(errorMessage); - arg.Value = new IntValue(_maxNumberOfResults); // if disabled mode we just log info and override the arg to be maxvalue - } - } - } - }); - }); + // if (value.HasValue && value > _maxNumberOfResults) + // { + // var localizer = context.ServiceProvider.GetService>(); + // var errorMessage = localizer["'{0}' exceeds the maximum number of results for '{1}' ({2})", value.Value, arg.Name, _maxNumberOfResults]; + + // if (_maxNumberOfResultsValidationMode == MaxNumberOfResultsValidationMode.Enabled) + // { + // validationContext.ReportError(new ValidationError( + // validationContext.OriginalQuery, + // "ArgumentInputError", + // errorMessage, + // arg)); + // } + // else + // { + // var logger = context.ServiceProvider.GetService>(); + // logger.LogInformation(errorMessage); + // arg.Value = new IntValue(_maxNumberOfResults); // if disabled mode we just log info and override the arg to be maxvalue + // } + // } + // } + // }); + //}); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs index 33bfeaa6fa1..71853b43045 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Threading.Tasks; using GraphQL.Language.AST; using GraphQL.Types; using GraphQL.Validation; @@ -12,50 +13,56 @@ public class RequiresPermissionValidationRule : IValidationRule { public static readonly string ErrorCode = "Unauthorized"; - public INodeVisitor Validate(ValidationContext validationContext) + public Task ValidateAsync(ValidationContext validationContext) { - var context = (GraphQLContext)validationContext.UserContext; + var context = (GraphQLUserContext)validationContext.UserContext; - return new EnterLeaveListener(_ => - { - _.Match(op => - { - if (op.OperationType == OperationType.Mutation) - { - var authorizationManager = context.ServiceProvider.GetService(); + // Todo: EnterLeaveListener has been removed and the signatures of INodeVisitor.Enter and INodeVisitor.Leave have changed. NodeVisitors class has been added in its place. + // https://graphql-dotnet.github.io/docs/migrations/migration4/ + // Ex: https://github.com/graphql-dotnet/graphql-dotnet/issues/2406 + INodeVisitor result = new NodeVisitors(); + return Task.FromResult(result); - if (!authorizationManager.AuthorizeAsync(context.User, Permissions.ExecuteGraphQLMutations).GetAwaiter().GetResult()) - { - var localizer = context.ServiceProvider.GetService>(); + //return new EnterLeaveListener(_ => + //{ + // _.Match(op => + // { + // if (op.OperationType == OperationType.Mutation) + // { + // var authorizationManager = context.ServiceProvider.GetService(); - validationContext.ReportError(new ValidationError( - validationContext.OriginalQuery, - ErrorCode, - localizer["Authorization is required to access {0}.", op.Name], - op)); - } - } - }); + // if (!authorizationManager.AuthorizeAsync(context.User, Permissions.ExecuteGraphQLMutations).GetAwaiter().GetResult()) + // { + // var localizer = context.ServiceProvider.GetService>(); - _.Match(fieldAst => - { - var fieldDef = validationContext.TypeInfo.GetFieldDef(); + // validationContext.ReportError(new ValidationError( + // validationContext.OriginalQuery, + // ErrorCode, + // localizer["Authorization is required to access {0}.", op.Name], + // op)); + // } + // } + // }); - if (fieldDef.HasPermissions() && !Authorize(fieldDef, context)) - { - var localizer = context.ServiceProvider.GetService>(); + // _.Match(fieldAst => + // { + // var fieldDef = validationContext.TypeInfo.GetFieldDef(); - validationContext.ReportError(new ValidationError( - validationContext.OriginalQuery, - ErrorCode, - localizer["Authorization is required to access the field. {0}", fieldAst.Name], - fieldAst)); - } - }); - }); + // if (fieldDef.HasPermissions() && !Authorize(fieldDef, context)) + // { + // var localizer = context.ServiceProvider.GetService>(); + + // validationContext.ReportError(new ValidationError( + // validationContext.OriginalQuery, + // ErrorCode, + // localizer["Authorization is required to access the field. {0}", fieldAst.Name], + // fieldAst)); + // } + // }); + //}); } - private static bool Authorize(IProvideMetadata type, GraphQLContext context) + private static bool Authorize(IProvideMetadata type, GraphQLUserContext context) { var authorizationManager = context.ServiceProvider.GetService(); diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs index 979de4a6885..fbd207433e7 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs @@ -35,7 +35,7 @@ public FieldType GetField(ContentPartFieldDefinition field) Type = queryGraphType, Resolver = new FuncFieldResolver(context => { - var typeToResolve = context.ReturnType.GetType().BaseType.GetGenericArguments().First(); + var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); // ReturnType.GetType().BaseType.GetGenericArguments().First(); // Check if part has been collapsed by trying to get the parent part. var contentPart = context.Source.Get(typeof(ContentPart), field.PartDefinition.Name); diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/HtmlFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/HtmlFieldQueryObjectType.cs index bd135063781..9b0bafe8a46 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/HtmlFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/HtmlFieldQueryObjectType.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Fluid; using Fluid.Values; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; @@ -33,7 +34,7 @@ public HtmlFieldQueryObjectType(IStringLocalizer S) .ResolveLockedAsync(RenderHtml); } - private static async Task RenderHtml(ResolveFieldContext ctx) + private static async Task RenderHtml(IResolveFieldContext ctx) { var serviceProvider = ctx.ResolveServiceProvider(); var shortcodeService = serviceProvider.GetRequiredService(); diff --git a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationQueryObjectType.cs index 9c8b681c0cf..c6e6e385b5b 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationQueryObjectType.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; diff --git a/src/OrchardCore.Modules/OrchardCore.Demo/Services/UserProfileClaimsProvider.cs b/src/OrchardCore.Modules/OrchardCore.Demo/Services/UserProfileClaimsProvider.cs index 3e3c0bddbbd..11530780e68 100644 --- a/src/OrchardCore.Modules/OrchardCore.Demo/Services/UserProfileClaimsProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Demo/Services/UserProfileClaimsProvider.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using GraphQL; using OrchardCore.Demo.Models; +using OrchardCore.Entities; using OrchardCore.Users; using OrchardCore.Users.Models; using OrchardCore.Users.Services; diff --git a/src/OrchardCore.Modules/OrchardCore.Html/GraphQL/HtmlBodyQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Html/GraphQL/HtmlBodyQueryObjectType.cs index dd1c49858fa..c80963d7591 100644 --- a/src/OrchardCore.Modules/OrchardCore.Html/GraphQL/HtmlBodyQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Html/GraphQL/HtmlBodyQueryObjectType.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Fluid; using Fluid.Values; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; @@ -31,7 +32,7 @@ public HtmlBodyQueryObjectType(IStringLocalizer S) .ResolveLockedAsync(RenderHtml); } - private static async Task RenderHtml(ResolveFieldContext ctx) + private static async Task RenderHtml(IResolveFieldContext ctx) { var serviceProvider = ctx.ResolveServiceProvider(); var shortcodeService = serviceProvider.GetRequiredService(); diff --git a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs index 2ca37536269..ac6bd2e6a6c 100644 --- a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using OrchardCore.Apis.GraphQL; @@ -35,7 +36,7 @@ public LayerQueryObjectType() .Argument("status", "publication status of the widgets") .ResolveLockedAsync(async ctx => { - var context = (GraphQLContext)ctx.UserContext; + var context = (GraphQLUserContext)ctx.UserContext; var layerService = context.ServiceProvider.GetService(); var filter = GetVersionFilter(ctx.GetArgument("status")); diff --git a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/SiteLayersQuery.cs b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/SiteLayersQuery.cs index e0a9b6c2ba4..1babadb32ad 100644 --- a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/SiteLayersQuery.cs +++ b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/SiteLayersQuery.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; @@ -37,9 +38,9 @@ public Task BuildAsync(ISchema schema) return Task.CompletedTask; } - private async Task> ResolveAsync(ResolveFieldContext resolveContext) + private async Task> ResolveAsync(IResolveFieldContext resolveContext) { - var layerService = resolveContext.ResolveServiceProvider().GetService(); + var layerService = resolveContext.RequestServices.GetService(); var allLayers = await layerService.GetLayersAsync(); return allLayers.Layers; } diff --git a/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ListQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ListQueryObjectType.cs index 2b04a597c9e..7f9f147b163 100644 --- a/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ListQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ListQueryObjectType.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using GraphQL; using GraphQL.DataLoader; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; @@ -36,9 +37,9 @@ public ListQueryObjectType(IStringLocalizer S) var dataLoader = accessor.Context.GetOrAddCollectionBatchLoader("ContainedPublishedContentItems", x => LoadPublishedContentItemsForListAsync(x, session)); - return (await dataLoader.LoadAsync(g.Source.ContentItem.ContentItemId)) + return ((await dataLoader.LoadAsync(g.Source.ContentItem.ContentItemId).GetResultAsync()) .Skip(g.GetArgument("skip")) - .Take(g.GetArgument("first")); + .Take(g.GetArgument("first"))); }); } diff --git a/src/OrchardCore.Modules/OrchardCore.Localization/GraphQL/SiteCulturesQuery.cs b/src/OrchardCore.Modules/OrchardCore.Localization/GraphQL/SiteCulturesQuery.cs index e86e59ef2ba..71a6d8b5963 100644 --- a/src/OrchardCore.Modules/OrchardCore.Localization/GraphQL/SiteCulturesQuery.cs +++ b/src/OrchardCore.Modules/OrchardCore.Localization/GraphQL/SiteCulturesQuery.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; @@ -44,9 +45,9 @@ public Task BuildAsync(ISchema schema) return Task.CompletedTask; } - private async Task> ResolveAsync(ResolveFieldContext resolveContext) + private async Task> ResolveAsync(IResolveFieldContext resolveContext) { - var localizationService = resolveContext.ResolveServiceProvider().GetService(); + var localizationService = resolveContext.RequestServices.GetService(); var defaultCulture = await localizationService.GetDefaultCultureAsync(); var supportedCultures = await localizationService.GetSupportedCulturesAsync(); diff --git a/src/OrchardCore.Modules/OrchardCore.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs index 362a19a24f4..3ac3cff2197 100644 --- a/src/OrchardCore.Modules/OrchardCore.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using GraphQL; using GraphQL.Types; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; diff --git a/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownBodyQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownBodyQueryObjectType.cs index cb7269da9bf..14df1285664 100644 --- a/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownBodyQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownBodyQueryObjectType.cs @@ -3,6 +3,7 @@ using System.Text.Encodings.Web; using System.Threading.Tasks; using Fluid.Values; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; @@ -34,7 +35,7 @@ public MarkdownBodyQueryObjectType(IStringLocalizer .ResolveLockedAsync(ToHtml); } - private static async Task ToHtml(ResolveFieldContext ctx) + private static async Task ToHtml(IResolveFieldContext ctx) { if (string.IsNullOrEmpty(ctx.Source.Markdown)) { diff --git a/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownFieldQueryObjectType.cs index 11b1e5ba804..8f4a1061f03 100644 --- a/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownFieldQueryObjectType.cs @@ -3,6 +3,7 @@ using System.Text.Encodings.Web; using System.Threading.Tasks; using Fluid.Values; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; @@ -36,7 +37,7 @@ public MarkdownFieldQueryObjectType(IStringLocalizer ToHtml(ResolveFieldContext ctx) + private static async Task ToHtml(IResolveFieldContext ctx) { if (string.IsNullOrEmpty(ctx.Source.Markdown)) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs index 22738a98779..7c458d76a9b 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs @@ -19,7 +19,7 @@ public MediaAssetObjectType() .Resolve(x => { var path = x.Source.Path; - var context = (GraphQLContext)x.UserContext; + var context = (GraphQLUserContext)x.UserContext; var mediaFileStore = context.ServiceProvider.GetService(); return mediaFileStore.MapPathToPublicUrl(path); }); diff --git a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetQuery.cs b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetQuery.cs index b5d1dfdab41..eb5cae2447d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetQuery.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetQuery.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; @@ -49,9 +50,9 @@ public Task BuildAsync(ISchema schema) return Task.CompletedTask; } - private async Task> ResolveAsync(ResolveFieldContext resolveContext) + private async Task> ResolveAsync(IResolveFieldContext resolveContext) { - var mediaFileStore = resolveContext.ResolveServiceProvider().GetService(); + var mediaFileStore = resolveContext.RequestServices.GetService(); var path = resolveContext.GetArgument("path", string.Empty); var includeSubDirectories = resolveContext.GetArgument("includeSubDirectories", false); diff --git a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs index 6d23382f5d0..367d601245d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs @@ -26,7 +26,7 @@ public MediaFieldQueryObjectType() .Resolve(x => { var paths = x.Page(x.Source.Paths); - var context = (GraphQLContext)x.UserContext; + var context = (GraphQLUserContext)x.UserContext; var mediaFileStore = context.ServiceProvider.GetService(); return paths.Select(p => mediaFileStore.MapPathToPublicUrl(p)); }); diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs index 1aaa0263619..7e338fc880c 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using GraphQL; using GraphQL.Types; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/FieldBuilderResolverExtensions.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/FieldBuilderResolverExtensions.cs index e364f6244a7..0a905fff9e8 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/FieldBuilderResolverExtensions.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/FieldBuilderResolverExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using GraphQL; using GraphQL.Builders; using GraphQL.Types; using OrchardCore.Apis.GraphQL.Resolvers; @@ -8,7 +9,7 @@ namespace OrchardCore.Apis.GraphQL { public static class FieldBuilderResolverExtensions { - public static FieldBuilder ResolveLockedAsync(this FieldBuilder builder, Func, Task> resolve) + public static FieldBuilder ResolveLockedAsync(this FieldBuilder builder, Func, Task> resolve) { return builder.Resolve(new LockedAsyncFieldResolver(resolve)); } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs index bc5ff679fe7..2a028a84799 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using GraphQL; using GraphQL.Builders; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; @@ -10,11 +11,21 @@ namespace OrchardCore.Apis.GraphQL { public static class ResolveFieldContextExtensions { - public static bool HasPopulatedArgument(this ResolveFieldContext source, string argumentName) + public static bool HasPopulatedArgument(this IResolveFieldContext source, string argumentName) { if (source.Arguments?.ContainsKey(argumentName) ?? false) { - return !string.IsNullOrEmpty(source.Arguments[argumentName]?.ToString()); + return !string.IsNullOrEmpty(source.Arguments[argumentName].ToString()); + }; + + return false; + } + + public static bool HasPopulatedArgument(this IResolveFieldContext source, string argumentName) + { + if (source.Arguments?.ContainsKey(argumentName) ?? false) + { + return !string.IsNullOrEmpty(source.Arguments[argumentName].ToString()); }; return false; @@ -28,7 +39,7 @@ public static FieldBuilder PagingArguments("skip", "the number of elements to skip", 0); } - public static IEnumerable Page(this ResolveFieldContext context, IEnumerable source) + public static IEnumerable Page(this IResolveFieldContext context, IEnumerable source) { var skip = context.GetArgument("skip"); var first = context.GetArgument("first"); @@ -59,9 +70,9 @@ public static IEnumerable Page(this ResolveFieldContext return source; } - public static IServiceProvider ResolveServiceProvider(this ResolveFieldContext context) + public static IServiceProvider ResolveServiceProvider(this IResolveFieldContext context) { - return ((GraphQLContext)context.UserContext).ServiceProvider; + return ((GraphQLUserContext)context.UserContext).ServiceProvider; } } } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs index 1a676379641..d6ef9a4f475 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs @@ -1,11 +1,12 @@ using System; +using System.Collections.Generic; using System.Security.Claims; using System.Threading; using Microsoft.AspNetCore.Http; namespace OrchardCore.Apis.GraphQL { - public class GraphQLContext + public class GraphQLUserContext : Dictionary { public HttpContext HttpContext { get; set; } public ClaimsPrincipal User { get; set; } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/OrchardCore.Apis.GraphQL.Abstractions.csproj b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/OrchardCore.Apis.GraphQL.Abstractions.csproj index 3dc3d71806b..b4b0721898a 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/OrchardCore.Apis.GraphQL.Abstractions.csproj +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/OrchardCore.Apis.GraphQL.Abstractions.csproj @@ -17,6 +17,7 @@ + diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Resolvers/LockedAsyncFieldResolver.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Resolvers/LockedAsyncFieldResolver.cs index 9953918a33e..aec3c67fb0d 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Resolvers/LockedAsyncFieldResolver.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Resolvers/LockedAsyncFieldResolver.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using GraphQL; using GraphQL.Resolvers; using GraphQL.Types; @@ -7,16 +8,16 @@ namespace OrchardCore.Apis.GraphQL.Resolvers { public class LockedAsyncFieldResolver : IFieldResolver> { - private readonly Func> _resolver; + private readonly Func> _resolver; - public LockedAsyncFieldResolver(Func> resolver) + public LockedAsyncFieldResolver(Func> resolver) { _resolver = resolver; } - public async Task Resolve(ResolveFieldContext context) + public async Task Resolve(IResolveFieldContext context) { - var graphContext = (GraphQLContext)context.UserContext; + var graphContext = (GraphQLUserContext)context.UserContext; await graphContext.ExecutionContextLock.WaitAsync(); @@ -30,7 +31,7 @@ public async Task Resolve(ResolveFieldContext context) } } - object IFieldResolver.Resolve(ResolveFieldContext context) + object IFieldResolver.Resolve(IResolveFieldContext context) { return Resolve(context); } @@ -38,13 +39,13 @@ object IFieldResolver.Resolve(ResolveFieldContext context) public class LockedAsyncFieldResolver : AsyncFieldResolver, IFieldResolver> { - public LockedAsyncFieldResolver(Func, Task> resolver) : base(resolver) + public LockedAsyncFieldResolver(Func, Task> resolver) : base(resolver) { } - public new async Task Resolve(ResolveFieldContext context) + public new async Task Resolve(IResolveFieldContext context) { - var graphContext = (GraphQLContext)context.UserContext; + var graphContext = (GraphQLUserContext)context.UserContext; await graphContext.ExecutionContextLock.WaitAsync(); try @@ -57,7 +58,7 @@ public LockedAsyncFieldResolver(Func, Task GetOrAddPublishedContentItemByIdDataLoader(this ResolveFieldContext context) + public static IDataLoader GetOrAddPublishedContentItemByIdDataLoader(this IResolveFieldContext context) { var serviceProvider = context.ResolveServiceProvider(); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs index 51e174aa90c..2853846f87b 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using GraphQL; using GraphQL.Resolvers; using GraphQL.Types; using Microsoft.AspNetCore.Http; @@ -47,7 +48,7 @@ public Task BuildAsync(ISchema schema) return Task.CompletedTask; } - private Task ResolveAsync(ResolveFieldContext context) + private Task ResolveAsync(IResolveFieldContext context) { var contentItemId = context.GetArgument("contentItemId"); var contentManager = _httpContextAccessor.HttpContext.RequestServices.GetService(); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index 2f075eac564..9fb6cc6045f 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; +using GraphQL; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -64,9 +65,9 @@ public ContentItemsFieldType(string contentItemName, ISchema schema, IOptions> Resolve(ResolveFieldContext context) + private async Task> Resolve(IResolveFieldContext context) { - var graphContext = (GraphQLContext)context.UserContext; + var graphContext = (GraphQLUserContext)context.UserContext; var versionOption = VersionOptions.Published; @@ -114,9 +115,9 @@ private async Task> Resolve(ResolveFieldContext context private IQuery FilterWhereArguments( IQuery query, JObject where, - ResolveFieldContext fieldContext, + IResolveFieldContext fieldContext, ISession session, - GraphQLContext context) + GraphQLUserContext context) { if (where == null) { @@ -183,7 +184,7 @@ private IQuery FilterWhereArguments( return contentQuery; } - private IQuery PageQuery(IQuery contentItemsQuery, ResolveFieldContext context, GraphQLContext graphQLContext) + private IQuery PageQuery(IQuery contentItemsQuery, IResolveFieldContext context, GraphQLUserContext graphQLContext) { var first = context.GetArgument("first"); @@ -216,9 +217,15 @@ private VersionOptions GetVersionOption(PublicationStatusEnum status) } } - private static IQuery FilterContentType(IQuery query, ResolveFieldContext context) + private static IQuery FilterContentType(IQuery query, IResolveFieldContext context) { - var contentType = ((ListGraphType)context.ReturnType).ResolvedType.Name; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + //var contentType = ((ListGraphType)context.ReturnType).ResolvedType.Name; + var contentType = context.FieldDefinition.ResolvedType.Name; return query.Where(q => q.ContentType == contentType); } @@ -241,7 +248,7 @@ private static IQuery FilterVersion(IQuery indexAliases) + private void BuildWhereExpressions(JToken where, Junction expressions, string tableAlias, IResolveFieldContext fieldContext, IDictionary indexAliases) { if (where is JArray array) { @@ -259,7 +266,7 @@ private void BuildWhereExpressions(JToken where, Junction expressions, string ta } } - private void BuildExpressionsInternal(JObject where, Junction expressions, string tableAlias, ResolveFieldContext fieldContext, IDictionary indexAliases) + private void BuildExpressionsInternal(JObject where, Junction expressions, string tableAlias, IResolveFieldContext fieldContext, IDictionary indexAliases) { foreach (var entry in where.Properties()) { @@ -359,7 +366,7 @@ private void BuildExpressionsInternal(JObject where, Junction expressions, strin } private IQuery OrderBy(IQuery query, - ResolveFieldContext context) + IResolveFieldContext context) { if (context.HasPopulatedArgument("orderBy")) { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/GraphQLFilter.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/GraphQLFilter.cs index 26f9eecd54c..ccd9c1362e0 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/GraphQLFilter.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/GraphQLFilter.cs @@ -1,18 +1,18 @@ using System.Collections.Generic; using System.Threading.Tasks; -using GraphQL.Types; +using GraphQL; using YesSql; namespace OrchardCore.ContentManagement.GraphQL.Queries { public abstract class GraphQLFilter : IGraphQLFilter where TSourceType : class { - public virtual Task> PreQueryAsync(IQuery query, ResolveFieldContext context) + public virtual Task> PreQueryAsync(IQuery query, IResolveFieldContext context) { return Task.FromResult(query); } - public virtual Task> PostQueryAsync(IEnumerable contentItems, ResolveFieldContext context) + public virtual Task> PostQueryAsync(IEnumerable contentItems, IResolveFieldContext context) { return Task.FromResult(contentItems); } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/IGraphQLFilter.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/IGraphQLFilter.cs index 1fe5469bed6..af7a0c5254d 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/IGraphQLFilter.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/IGraphQLFilter.cs @@ -1,14 +1,14 @@ using System.Collections.Generic; using System.Threading.Tasks; -using GraphQL.Types; +using GraphQL; using YesSql; namespace OrchardCore.ContentManagement.GraphQL.Queries { public interface IGraphQLFilter where TSourceType : class { - Task> PreQueryAsync(IQuery query, ResolveFieldContext context); + Task> PreQueryAsync(IQuery query, IResolveFieldContext context); - Task> PostQueryAsync(IEnumerable contentItems, ResolveFieldContext context); + Task> PostQueryAsync(IEnumerable contentItems, IResolveFieldContext context); } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs index 09bf17aab49..492d50ddf13 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs @@ -37,7 +37,7 @@ public ContentItemType(IOptions optionsAccessor) .Name("render") .ResolveLockedAsync(async context => { - var userContext = (GraphQLContext)context.UserContext; + var userContext = (GraphQLUserContext)context.UserContext; var serviceProvider = userContext.ServiceProvider; // Build shape diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index 7a1fdb9c277..64b5f5d416b 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -3,6 +3,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using GraphQL; +using GraphQL.Execution; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -136,27 +138,29 @@ public async Task ShouldFilterByContentItemIndex() var animalWhereInput = new AnimalPartWhereInput(); var inputs = new FieldType { Name = "Inputs", Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; + var a = new GraphQLUserContext + { + ServiceProvider = services + }; + var context = new ResolveFieldContext { - Arguments = new Dictionary(), - UserContext = new GraphQLContext - { - ServiceProvider = services - }, - ReturnType = returnType, + Arguments = new Dictionary(), + UserContext = null, + //ReturnType = returnType, FieldDefinition = inputs }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLContext)context.UserContext).ServiceProvider.GetService(); + var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); - context.Arguments["where"] = JObject.Parse("{ contentItemId: \"1\" }"); + context.Arguments["where"] = new ArgumentValue(JObject.Parse("{ contentItemId: \"1\" }"), ArgumentSource.Variable); var dogs = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); Assert.Single(dogs); @@ -191,25 +195,25 @@ public async Task ShouldFilterByContentItemIndexWhenSqlTablePrefixIsUsed() var context = new ResolveFieldContext { - Arguments = new Dictionary(), - UserContext = new GraphQLContext + Arguments = new Dictionary(), + UserContext = new GraphQLUserContext { ServiceProvider = services }, - ReturnType = returnType, + //ReturnType = returnType, FieldDefinition = inputs }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLContext)context.UserContext).ServiceProvider.GetService(); + var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); - context.Arguments["where"] = JObject.Parse("{ contentItemId: \"1\" }"); + context.Arguments["where"] = new ArgumentValue(JObject.Parse("{ contentItemId: \"1\" }"), ArgumentSource.Variable); var dogs = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); Assert.Single(dogs); @@ -247,25 +251,25 @@ public async Task ShouldFilterByAliasIndexRegardlessOfInputFieldCase(string fiel var context = new ResolveFieldContext { - Arguments = new Dictionary(), - UserContext = new GraphQLContext + Arguments = new Dictionary(), + UserContext = new GraphQLUserContext { ServiceProvider = services }, - ReturnType = returnType, + //ReturnType = returnType, FieldDefinition = inputs }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLContext)context.UserContext).ServiceProvider.GetService(); + var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); - context.Arguments["where"] = JObject.Parse(string.Concat("{ ", fieldName, ": { name: \"doug\" } }")); + context.Arguments["where"] = new ArgumentValue(JObject.Parse(string.Concat("{ ", fieldName, ": { name: \"doug\" } }")), ArgumentSource.Variable); var dogs = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); Assert.Single(dogs); @@ -290,35 +294,35 @@ public async Task ShouldBeAbleToUseTheSameIndexForMultipleAliases() services.Build(); - var retrunType = new ListGraphType(); - retrunType.ResolvedType = new StringGraphType() { Name = "Animal" }; + var returnType = new ListGraphType(); + returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; var context = new ResolveFieldContext { - Arguments = new Dictionary(), - UserContext = new GraphQLContext + Arguments = new Dictionary(), + UserContext = new GraphQLUserContext { ServiceProvider = services }, - ReturnType = retrunType + //ReturnType = returnType }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new Animal { Name = "doug" }); - var session = ((GraphQLContext)context.UserContext).ServiceProvider.GetService(); + var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); - context.Arguments["where"] = JObject.Parse("{ cats: { name: \"doug\" } }"); + context.Arguments["where"] = new ArgumentValue(JObject.Parse("{ cats: { name: \"doug\" } }"), ArgumentSource.Variable); var cats = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); Assert.Single(cats); Assert.Equal("doug", cats.First().As().Name); - context.Arguments["where"] = JObject.Parse("{ dogs: { name: \"doug\" } }"); + context.Arguments["where"] = new ArgumentValue(JObject.Parse("{ dogs: { name: \"doug\" } }"), ArgumentSource.Variable); var dogs = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); Assert.Single(dogs); @@ -346,17 +350,17 @@ public async Task ShouldFilterOnMultipleIndexesOnSameAlias() services.Services.AddSingleton>(); services.Build(); - var retrunType = new ListGraphType(); - retrunType.ResolvedType = new StringGraphType() { Name = "Animal" }; + var returnType = new ListGraphType(); + returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; var context = new ResolveFieldContext { - Arguments = new Dictionary(), - UserContext = new GraphQLContext + Arguments = new Dictionary(), + UserContext = new GraphQLUserContext { ServiceProvider = services }, - ReturnType = retrunType + //ReturnType = returnType }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; @@ -368,7 +372,7 @@ public async Task ShouldFilterOnMultipleIndexesOnSameAlias() var ci2 = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "3", ContentItemVersionId = "3" }; ci2.Weld(new Animal { Name = "tommy", IsHappy = false, IsScary = true }); - var session = ((GraphQLContext)context.UserContext).ServiceProvider.GetService(); + var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); session.Save(ci1); session.Save(ci2); @@ -376,7 +380,7 @@ public async Task ShouldFilterOnMultipleIndexesOnSameAlias() var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); - context.Arguments["where"] = JObject.Parse("{ animals: { name: \"doug\", isScary: true } }"); + context.Arguments["where"] = new ArgumentValue(JObject.Parse("{ animals: { name: \"doug\", isScary: true } }"), ArgumentSource.Variable); var animals = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); Assert.Single(animals); @@ -410,25 +414,25 @@ public async Task ShouldFilterPartsWithoutAPrefixWhenThePartHasNoPrefix() var context = new ResolveFieldContext { - Arguments = new Dictionary(), - UserContext = new GraphQLContext + Arguments = new Dictionary(), + UserContext = new GraphQLUserContext { ServiceProvider = services }, - ReturnType = returnType, + //ReturnType = returnType, FieldDefinition = inputs }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLContext)context.UserContext).ServiceProvider.GetService(); + var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); - context.Arguments["where"] = JObject.Parse("{ animal: { name: \"doug\" } }"); + context.Arguments["where"] = new ArgumentValue(JObject.Parse("{ animal: { name: \"doug\" } }"), ArgumentSource.Variable); var dogs = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); Assert.Single(dogs); @@ -459,12 +463,12 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() var context = new ResolveFieldContext { - Arguments = new Dictionary(), - UserContext = new GraphQLContext + Arguments = new Dictionary(), + UserContext = new GraphQLUserContext { ServiceProvider = services }, - ReturnType = returnType, + //ReturnType = returnType, FieldDefinition = new FieldType { Name = "Inputs", @@ -483,13 +487,13 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLContext)context.UserContext).ServiceProvider.GetService(); + var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); - context.Arguments["where"] = JObject.Parse("{ name: \"doug\" }"); + context.Arguments["where"] = new ArgumentValue(JObject.Parse("{ name: \"doug\" }"), ArgumentSource.Variable); var dogs = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); Assert.Single(dogs); @@ -504,14 +508,18 @@ public AnimalPartWhereInput() { Name = "Test"; Description = "Foo"; - AddField(new FieldType { Name = "Animal", Type = typeof(StringGraphType), Metadata = new Dictionary { { "PartName", "AnimalPart" } } }); + var fieldType = new FieldType { Name = "Animal", Type = typeof(StringGraphType) }; + fieldType.Metadata["PartName"] = "AnimalPart"; + AddField(fieldType); } public AnimalPartWhereInput(string fieldName) { Name = "Test"; Description = "Foo"; - AddField(new FieldType { Name = fieldName, Type = typeof(StringGraphType), Metadata = new Dictionary { { "PartName", "AnimalPart" } } }); + var fieldType = new FieldType { Name = fieldName, Type = typeof(StringGraphType) }; + fieldType.Metadata["PartName"] = "AnimalPart"; + AddField(fieldType); } } @@ -521,7 +529,10 @@ public AnimalPartCollapsedWhereInput() { Name = "Test"; Description = "Foo"; - AddField(new FieldType { Name = "Name", Type = typeof(StringGraphType), Metadata = new Dictionary { { "PartName", "AnimalPart" }, { "PartCollapsed", true } } }); + var fieldType = new FieldType { Name = "Name", Type = typeof(StringGraphType) }; + fieldType.Metadata["PartName"] = "AnimalPart"; + fieldType.Metadata["PartCollapse"] = true; + AddField(fieldType); } } diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs index 6d5deff1085..8aa60a77020 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs @@ -119,12 +119,12 @@ private ExecutionOptions BuildExecutionOptions(string query, PermissionsContext { Query = query, Schema = new ValidationSchema(), - UserContext = new GraphQLContext + UserContext = new GraphQLUserContext { ServiceProvider = serviceProvider, User = new ClaimsPrincipal(new StubIdentity()) }, - ValidationRules = DocumentValidator.CoreRules().Concat(serviceProvider.GetServices()) + ValidationRules = DocumentValidator.CoreRules.Concat(serviceProvider.GetServices()) }; } @@ -132,9 +132,9 @@ private class ValidationSchema : Schema { public ValidationSchema() { - RegisterType(); + RegisterType(typeof(TestField)); Query = new ValidationQueryRoot { Name = "Query" }; - FieldNameConverter = new CamelCaseFieldNameConverter(); + NameConverter = new CamelCaseNameConverter(); } } From f42ac111c649f020df60a46d165d4687288a95fd Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 01:12:02 +0100 Subject: [PATCH 02/29] started on dataloaders, removed no longer required serviceprovider on usercontext --- .../OrchardCore.Apis.GraphQL/Startup.cs | 2 -- .../RequiresPermissionValidationRule.cs | 8 +++--- .../ContentPickerFieldQueryObjectType.cs | 11 ++++++-- .../GraphQL/HtmlBodyQueryObjectType.cs | 9 +++---- .../GraphQL/LayerQueryObjectType.cs | 3 +-- .../GraphQL/MarkdownBodyQueryObjectType.cs | 2 +- .../Extensions/DataLoaderExtensions.cs | 27 ------------------- .../ResolveFieldContextExtensions.cs | 7 +---- .../GraphQLUserContext.cs | 4 +-- .../Extensions/DataLoaderExtensions.cs | 17 +++++------- .../Queries/ContentItemsFieldType.cs | 21 +++++++-------- 11 files changed, 38 insertions(+), 73 deletions(-) delete mode 100644 src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/DataLoaderExtensions.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs index 2cda9f2477b..70e8597706d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs @@ -63,9 +63,7 @@ public override void ConfigureServices(IServiceCollection services) c.BuildUserContext = ctx => new GraphQLUserContext { - HttpContext = ctx, User = ctx.User, - ServiceProvider = ctx.RequestServices }; c.ExposeExceptions = exposeExceptions; c.MaxDepth = configuration.GetValue($"OrchardCore_Apis_GraphQL:{nameof(GraphQLSettings.MaxDepth)}") ?? 20; diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs index 71853b43045..ae3b64a6d94 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs @@ -64,11 +64,11 @@ public Task ValidateAsync(ValidationContext validationContext) private static bool Authorize(IProvideMetadata type, GraphQLUserContext context) { - var authorizationManager = context.ServiceProvider.GetService(); + //var authorizationManager = context.ServiceProvider.GetService(); - // awaitable IValidationRule in graphql dotnet is coming soon: - // https://github.com/graphql-dotnet/graphql-dotnet/issues/1140 - return type.GetPermissions().All(x => authorizationManager.AuthorizeAsync(context.User, x.Permission, x.Resource).GetAwaiter().GetResult()); + //// awaitable IValidationRule in graphql dotnet is coming soon: + //// https://github.com/graphql-dotnet/graphql-dotnet/issues/1140 + //return type.GetPermissions().All(x => authorizationManager.AuthorizeAsync(context.User, x.Permission, x.Resource).GetAwaiter().GetResult()); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs index 3e30fcc2acc..5edd4485a28 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs @@ -1,4 +1,7 @@ using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GraphQL.DataLoader; using GraphQL.Types; using OrchardCore.Apis.GraphQL; using OrchardCore.ContentFields.Fields; @@ -23,14 +26,18 @@ public ContentPickerFieldQueryObjectType() return x.Page(x.Source.ContentItemIds); }); - Field, ContentItem[]>() + Field, IEnumerable>() .Name("contentItems") .Description("the content items") .PagingArguments() .ResolveAsync(x => { var contentItemLoader = x.GetOrAddPublishedContentItemByIdDataLoader(); - return contentItemLoader.LoadAsync(x.Page(x.Source.ContentItemIds)); + + return (contentItemLoader.LoadAsync(x.Page(x.Source.ContentItemIds))).Then(itemResultSet => + { + return itemResultSet.SelectMany(x => x); + }); }); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Html/GraphQL/HtmlBodyQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Html/GraphQL/HtmlBodyQueryObjectType.cs index c80963d7591..bd39346764d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Html/GraphQL/HtmlBodyQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Html/GraphQL/HtmlBodyQueryObjectType.cs @@ -34,9 +34,8 @@ public HtmlBodyQueryObjectType(IStringLocalizer S) private static async Task RenderHtml(IResolveFieldContext ctx) { - var serviceProvider = ctx.ResolveServiceProvider(); - var shortcodeService = serviceProvider.GetRequiredService(); - var contentDefinitionManager = serviceProvider.GetRequiredService(); + var shortcodeService = ctx.RequestServices.GetRequiredService(); + var contentDefinitionManager = ctx.RequestServices.GetRequiredService(); var contentTypeDefinition = contentDefinitionManager.GetTypeDefinition(ctx.Source.ContentItem.ContentType); var contentTypePartDefinition = contentTypeDefinition.Parts.FirstOrDefault(x => string.Equals(x.PartDefinition.Name, "HtmlBodyPart")); @@ -52,8 +51,8 @@ private static async Task RenderHtml(IResolveFieldContext HtmlBodyPart = ctx.Source, ContentItem = ctx.Source.ContentItem }; - var liquidTemplateManager = serviceProvider.GetRequiredService(); - var htmlEncoder = serviceProvider.GetService(); + var liquidTemplateManager = ctx.RequestServices.GetRequiredService(); + var htmlEncoder = ctx.RequestServices.GetService(); html = await liquidTemplateManager.RenderStringAsync(html, htmlEncoder, model, new Dictionary { ["ContentItem"] = new ObjectValue(model.ContentItem) }); } diff --git a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs index ac6bd2e6a6c..b5cc710ae39 100644 --- a/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Layers/GraphQL/LayerQueryObjectType.cs @@ -36,8 +36,7 @@ public LayerQueryObjectType() .Argument("status", "publication status of the widgets") .ResolveLockedAsync(async ctx => { - var context = (GraphQLUserContext)ctx.UserContext; - var layerService = context.ServiceProvider.GetService(); + var layerService = ctx.RequestServices.GetService(); var filter = GetVersionFilter(ctx.GetArgument("status")); var widgets = await layerService.GetLayerWidgetsAsync(filter); diff --git a/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownBodyQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownBodyQueryObjectType.cs index 14df1285664..3244851b569 100644 --- a/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownBodyQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownBodyQueryObjectType.cs @@ -42,7 +42,7 @@ private static async Task ToHtml(IResolveFieldContext return ctx.Source.Markdown; } - var serviceProvider = ctx.ResolveServiceProvider(); + var serviceProvider = ctx.RequestServices; var markdownService = serviceProvider.GetRequiredService(); var shortcodeService = serviceProvider.GetRequiredService(); var contentDefinitionManager = serviceProvider.GetRequiredService(); diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/DataLoaderExtensions.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/DataLoaderExtensions.cs deleted file mode 100644 index 8b5e97ba13c..00000000000 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/DataLoaderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using GraphQL.DataLoader; - -namespace OrchardCore.Apis.GraphQL -{ - public static class DataLoaderExtensions - { - public static Task LoadAsync(this IDataLoader dataLoader, IEnumerable keys) - { - var tasks = new List>(); - - foreach (var key in keys) - { - tasks.Add(dataLoader.LoadAsync(key)); - } - - return Task.WhenAll(tasks); - } - - public static Task LoadAsync(this IDataLoader dataLoader, params TKey[] keys) - { - return dataLoader.LoadAsync(keys.AsEnumerable()); - } - } -} diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs index 2a028a84799..b19beb033c8 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs @@ -47,7 +47,7 @@ public static IEnumerable Page(this IResolveFieldContext if (last == 0 && first == 0) { - first = context.ResolveServiceProvider().GetService>().Value.DefaultNumberOfResults; + first = context.RequestServices.GetService>().Value.DefaultNumberOfResults; } if (last > 0) @@ -69,10 +69,5 @@ public static IEnumerable Page(this IResolveFieldContext return source; } - - public static IServiceProvider ResolveServiceProvider(this IResolveFieldContext context) - { - return ((GraphQLUserContext)context.UserContext).ServiceProvider; - } } } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs index d6ef9a4f475..a293c07af02 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs @@ -8,9 +8,9 @@ namespace OrchardCore.Apis.GraphQL { public class GraphQLUserContext : Dictionary { - public HttpContext HttpContext { get; set; } + // public HttpContext HttpContext { get; set; } public ClaimsPrincipal User { get; set; } - public IServiceProvider ServiceProvider { get; set; } + // public IServiceProvider ServiceProvider { get; set; } public SemaphoreSlim ExecutionContextLock { get; } = new SemaphoreSlim(1, 1); } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/DataLoaderExtensions.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/DataLoaderExtensions.cs index deb58917908..965f18f91c0 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/DataLoaderExtensions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/DataLoaderExtensions.cs @@ -4,7 +4,6 @@ using GraphQL; using GraphQL.DataLoader; using Microsoft.Extensions.DependencyInjection; -using OrchardCore.Apis.GraphQL; using OrchardCore.ContentManagement.Records; using YesSql; using YesSql.Services; @@ -13,25 +12,23 @@ namespace OrchardCore.ContentManagement.GraphQL { public static class DataLoaderExtensions { - public static IDataLoader GetOrAddPublishedContentItemByIdDataLoader(this IResolveFieldContext context) + public static IDataLoader> GetOrAddPublishedContentItemByIdDataLoader(this IResolveFieldContext context) { - var serviceProvider = context.ResolveServiceProvider(); + var accessor = context.RequestServices.GetRequiredService(); + var session = context.RequestServices.GetService(); - var accessor = serviceProvider.GetRequiredService(); - var session = serviceProvider.GetService(); - - return accessor.Context.GetOrAddBatchLoader("GetPublishedContentItemsById", ci => LoadPublishedContentItems(ci, session)); + return accessor.Context.GetOrAddCollectionBatchLoader("GetPublishedContentItemsById", ci => LoadPublishedContentItemsAsync(ci, session)); } - private static async Task> LoadPublishedContentItems(IEnumerable contentItemIds, ISession session) + public static async Task> LoadPublishedContentItemsAsync(IEnumerable contentItemIds, ISession session) { if (contentItemIds is null || !contentItemIds.Any()) { - return new Dictionary(); + return default; } var contentItemsLoaded = await session.Query(y => y.ContentItemId.IsIn(contentItemIds) && y.Published).ListAsync(); - return contentItemsLoaded.ToDictionary(k => k.ContentItemId, v => v); + return contentItemsLoaded.ToLookup(k => k.ContentItemId, v => v); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index 9fb6cc6045f..8baf7c49ea7 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -67,8 +67,6 @@ public ContentItemsFieldType(string contentItemName, ISchema schema, IOptions> Resolve(IResolveFieldContext context) { - var graphContext = (GraphQLUserContext)context.UserContext; - var versionOption = VersionOptions.Published; if (context.HasPopulatedArgument("status")) @@ -82,11 +80,11 @@ private async Task> Resolve(IResolveFieldContext contex where = JObject.FromObject(context.Arguments["where"]); } - var session = graphContext.ServiceProvider.GetService(); + var session = context.RequestServices.GetService(); var preQuery = session.Query(); - var filters = graphContext.ServiceProvider.GetServices>(); + var filters = context.RequestServices.GetServices>(); foreach (var filter in filters) { @@ -99,8 +97,8 @@ private async Task> Resolve(IResolveFieldContext contex query = FilterContentType(query, context); query = OrderBy(query, context); - var contentItemsQuery = FilterWhereArguments(query, where, context, session, graphContext); - contentItemsQuery = PageQuery(contentItemsQuery, context, graphContext); + var contentItemsQuery = FilterWhereArguments(query, where, context, session); + contentItemsQuery = PageQuery(contentItemsQuery, context); var contentItems = await contentItemsQuery.ListAsync(); @@ -116,8 +114,7 @@ private IQuery FilterWhereArguments( IQuery query, JObject where, IResolveFieldContext fieldContext, - ISession session, - GraphQLUserContext context) + ISession session) { if (where == null) { @@ -128,15 +125,15 @@ private IQuery FilterWhereArguments( IPredicateQuery predicateQuery = new PredicateQuery( dialect: session.Store.Configuration.SqlDialect, - shellSettings: context.ServiceProvider.GetService(), - propertyProviders: context.ServiceProvider.GetServices()); + shellSettings: fieldContext.RequestServices.GetService(), + propertyProviders: fieldContext.RequestServices.GetServices()); // Create the default table alias predicateQuery.CreateAlias("", nameof(ContentItemIndex)); predicateQuery.CreateTableAlias(nameof(ContentItemIndex), defaultTableAlias); // Add all provided table alias to the current predicate query - var providers = context.ServiceProvider.GetServices(); + var providers = fieldContext.RequestServices.GetServices(); var indexes = new Dictionary(StringComparer.OrdinalIgnoreCase); var indexAliases = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -184,7 +181,7 @@ private IQuery FilterWhereArguments( return contentQuery; } - private IQuery PageQuery(IQuery contentItemsQuery, IResolveFieldContext context, GraphQLUserContext graphQLContext) + private IQuery PageQuery(IQuery contentItemsQuery, IResolveFieldContext context) { var first = context.GetArgument("first"); From f2fdd2106b78dbfcbcd09c9c085eb2799b610a2f Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 01:16:50 +0100 Subject: [PATCH 03/29] updated some more requestservices references --- .../GraphQLMiddleware.cs | 2 +- .../RequiresPermissionValidationRule.cs | 16 +++++++++------- .../GraphQL/MarkdownFieldQueryObjectType.cs | 2 +- .../Extensions/FieldTypeExtensions.cs | 5 ----- .../Queries/Types/ContentItemType.cs | 3 +-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs index ec8d3d1b1ae..345d8b367c8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs @@ -166,7 +166,7 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic _.Inputs = request.Variables.ToInputs(); _.UserContext = _settings.BuildUserContext?.Invoke(context); _.ExposeExceptions = _settings.ExposeExceptions; - _.ValidationRules = DocumentValidator.CoreRules() + _.ValidationRules = DocumentValidator.CoreRules .Concat(context.RequestServices.GetServices()); _.ComplexityConfiguration = new ComplexityConfiguration { diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs index ae3b64a6d94..a5047d1fe62 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs @@ -62,13 +62,15 @@ public Task ValidateAsync(ValidationContext validationContext) //}); } - private static bool Authorize(IProvideMetadata type, GraphQLUserContext context) - { - //var authorizationManager = context.ServiceProvider.GetService(); + //private static bool Authorize(IProvideMetadata type, GraphQLUserContext context) + //{ + // //var authorizationManager = context.ServiceProvider.GetService(); - //// awaitable IValidationRule in graphql dotnet is coming soon: - //// https://github.com/graphql-dotnet/graphql-dotnet/issues/1140 - //return type.GetPermissions().All(x => authorizationManager.AuthorizeAsync(context.User, x.Permission, x.Resource).GetAwaiter().GetResult()); - } + // //// awaitable IValidationRule in graphql dotnet is coming soon: + // //// https://github.com/graphql-dotnet/graphql-dotnet/issues/1140 + // //return type.GetPermissions().All(x => authorizationManager.AuthorizeAsync(context.User, x.Permission, x.Resource).GetAwaiter().GetResult()); + + + //} } } diff --git a/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownFieldQueryObjectType.cs index 8f4a1061f03..b47b4a76da1 100644 --- a/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Markdown/GraphQL/MarkdownFieldQueryObjectType.cs @@ -44,7 +44,7 @@ private static async Task ToHtml(IResolveFieldContext ctx return ctx.Source.Markdown; } - var serviceProvider = ctx.ResolveServiceProvider(); + var serviceProvider = ctx.RequestServices; var markdownService = serviceProvider.GetRequiredService(); var shortcodeService = serviceProvider.GetRequiredService(); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/FieldTypeExtensions.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/FieldTypeExtensions.cs index d027ef2d65a..bf729d2978e 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/FieldTypeExtensions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/FieldTypeExtensions.cs @@ -22,11 +22,6 @@ internal static FieldType WithMetaData(this FieldType fieldType, string name, ob return null; } - if (fieldType.Metadata == null) - { - fieldType.Metadata = new Dictionary(); - } - if (!fieldType.HasMetadata(name)) { fieldType.Metadata.Add(name, value); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs index 492d50ddf13..d27a071c27d 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs @@ -37,8 +37,7 @@ public ContentItemType(IOptions optionsAccessor) .Name("render") .ResolveLockedAsync(async context => { - var userContext = (GraphQLUserContext)context.UserContext; - var serviceProvider = userContext.ServiceProvider; + var serviceProvider = context.RequestServices; // Build shape var displayManager = serviceProvider.GetRequiredService(); From a4c54b796d96dbf7f7e0a8a5176504086134163b Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 01:28:17 +0100 Subject: [PATCH 04/29] few type fixes --- .../Queries/Types/DynamicContentTypeBuilder.cs | 2 +- .../Queries/Types/TypedContentTypeBuilder.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index 1194aaabf6f..4574b8b2c5d 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -105,7 +105,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin resolve: context => { var nameToResolve = partName; - var typeToResolve = context.ReturnType.GetType().BaseType.GetGenericArguments().First(); + var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); return context.Source.Get(typeToResolve, nameToResolve); }); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs index 411ef7ac417..ea531c2f7b4 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs @@ -1,4 +1,5 @@ using System.Linq; +using GraphQL; using GraphQL.Resolvers; using GraphQL.Types; using Microsoft.AspNetCore.Http; @@ -83,7 +84,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin resolve: context => { var nameToResolve = partName; - var typeToResolve = context.ReturnType.GetType().BaseType.GetGenericArguments().First(); + var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); return context.Source.Get(typeToResolve, nameToResolve); }); From 4d80c308b8f1bf9a51147b7c41c2c25659224904 Mon Sep 17 00:00:00 2001 From: Antoine Griffard Date: Fri, 9 Apr 2021 10:05:29 +0200 Subject: [PATCH 05/29] More fixes --- .../GraphQL/Types/HtmlFieldQueryObjectType.cs | 2 +- .../GraphQL/LocalizationQueryObjectType.cs | 2 +- .../GraphQL/ListQueryObjectType.cs | 2 +- .../GraphQL/LuceneQueryFieldTypeProvider.cs | 4 +- .../GraphQL/MediaAssetObjectType.cs | 2 +- .../GraphQL/MediaFieldQueryObjectType.cs | 2 +- .../Sql/GraphQL/SqlQueryFieldTypeProvider.cs | 4 +- .../GraphQL/TaxonomyFieldQueryObjectType.cs | 4 +- .../GraphQL/ContentItemsFieldTypeTests.cs | 71 +++++++++---------- .../RequiresPermissionValidationRuleTests.cs | 2 +- 10 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/HtmlFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/HtmlFieldQueryObjectType.cs index 9b0bafe8a46..2334e32a727 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/HtmlFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/HtmlFieldQueryObjectType.cs @@ -36,7 +36,7 @@ public HtmlFieldQueryObjectType(IStringLocalizer S) private static async Task RenderHtml(IResolveFieldContext ctx) { - var serviceProvider = ctx.ResolveServiceProvider(); + var serviceProvider = ctx.RequestServices; var shortcodeService = serviceProvider.GetRequiredService(); var contentDefinitionManager = serviceProvider.GetRequiredService(); diff --git a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationQueryObjectType.cs index c6e6e385b5b..5d14461e9e8 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationQueryObjectType.cs @@ -28,7 +28,7 @@ public LocalizationQueryObjectType(IStringLocalizer .ResolveLockedAsync(async ctx => { var culture = ctx.GetArgument("culture"); - var contentLocalizationManager = ctx.ResolveServiceProvider().GetService(); + var contentLocalizationManager = ctx.RequestServices.GetService(); if (culture != null) { diff --git a/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ListQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ListQueryObjectType.cs index 7f9f147b163..71863aeb1ce 100644 --- a/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ListQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ListQueryObjectType.cs @@ -31,7 +31,7 @@ public ListQueryObjectType(IStringLocalizer S) .Argument("skip", "the number of elements to skip", 0) .ResolveAsync(async g => { - var serviceProvider = g.ResolveServiceProvider(); + var serviceProvider = g.RequestServices; var session = serviceProvider.GetService(); var accessor = serviceProvider.GetRequiredService(); diff --git a/src/OrchardCore.Modules/OrchardCore.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs index 3ac3cff2197..238ab12db2d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs @@ -141,7 +141,7 @@ private FieldType BuildSchemaBasedFieldType(LuceneQuery query, JToken querySchem ResolvedType = new ListGraphType(typetype), Resolver = new LockedAsyncFieldResolver(async context => { - var queryManager = context.ResolveServiceProvider().GetService(); + var queryManager = context.RequestServices.GetService(); var iquery = await queryManager.GetQueryAsync(query.Name); var parameters = context.GetArgument("parameters"); @@ -178,7 +178,7 @@ private FieldType BuildContentTypeFieldType(ISchema schema, string contentType, ResolvedType = typetype.ResolvedType, Resolver = new LockedAsyncFieldResolver(async context => { - var queryManager = context.ResolveServiceProvider().GetService(); + var queryManager = context.RequestServices.GetService(); var iquery = await queryManager.GetQueryAsync(query.Name); var parameters = context.GetArgument("parameters"); diff --git a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs index 7c458d76a9b..2feb08e9d2f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs @@ -20,7 +20,7 @@ public MediaAssetObjectType() { var path = x.Source.Path; var context = (GraphQLUserContext)x.UserContext; - var mediaFileStore = context.ServiceProvider.GetService(); + var mediaFileStore = x.RequestServices.GetService(); return mediaFileStore.MapPathToPublicUrl(path); }); diff --git a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs index 367d601245d..fcb72c1a6d2 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs @@ -27,7 +27,7 @@ public MediaFieldQueryObjectType() { var paths = x.Page(x.Source.Paths); var context = (GraphQLUserContext)x.UserContext; - var mediaFileStore = context.ServiceProvider.GetService(); + var mediaFileStore = x.RequestServices.GetService(); return paths.Select(p => mediaFileStore.MapPathToPublicUrl(p)); }); } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs index 7e338fc880c..4c4891f8e52 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs @@ -142,7 +142,7 @@ private FieldType BuildSchemaBasedFieldType(SqlQuery query, JToken querySchema, ResolvedType = new ListGraphType(typetype), Resolver = new LockedAsyncFieldResolver(async context => { - var queryManager = context.ResolveServiceProvider().GetService(); + var queryManager = context.RequestServices.GetService(); var iquery = await queryManager.GetQueryAsync(query.Name); var parameters = context.GetArgument("parameters"); @@ -179,7 +179,7 @@ private FieldType BuildContentTypeFieldType(ISchema schema, string contentType, ResolvedType = typetype.ResolvedType, Resolver = new LockedAsyncFieldResolver(async context => { - var queryManager = context.ResolveServiceProvider().GetService(); + var queryManager = context.RequestServices.GetService(); var iquery = await queryManager.GetQueryAsync(query.Name); var parameters = context.GetArgument("parameters"); diff --git a/src/OrchardCore.Modules/OrchardCore.Taxonomies/GraphQL/TaxonomyFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Taxonomies/GraphQL/TaxonomyFieldQueryObjectType.cs index 0465c7823f8..a25d2c08110 100644 --- a/src/OrchardCore.Modules/OrchardCore.Taxonomies/GraphQL/TaxonomyFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Taxonomies/GraphQL/TaxonomyFieldQueryObjectType.cs @@ -39,7 +39,7 @@ public TaxonomyFieldQueryObjectType() .ResolveLockedAsync(async x => { var ids = x.Page(x.Source.TermContentItemIds); - var contentManager = x.ResolveServiceProvider().GetService(); + var contentManager = x.RequestServices.GetService(); var taxonomy = await contentManager.GetAsync(x.Source.TaxonomyContentItemId); @@ -64,7 +64,7 @@ public TaxonomyFieldQueryObjectType() .Description("the taxonomy content item") .ResolveLockedAsync(x => { - var contentManager = x.ResolveServiceProvider().GetService(); + var contentManager = x.RequestServices.GetService(); return contentManager.GetAsync(x.Source.TaxonomyContentItemId); }); } diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index 64b5f5d416b..508710ee5c7 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -138,23 +138,22 @@ public async Task ShouldFilterByContentItemIndex() var animalWhereInput = new AnimalPartWhereInput(); var inputs = new FieldType { Name = "Inputs", Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; - var a = new GraphQLUserContext - { - ServiceProvider = services - }; + //var a = new GraphQLUserContext + //{ + // ServiceProvider = services + //}; var context = new ResolveFieldContext { Arguments = new Dictionary(), UserContext = null, - //ReturnType = returnType, FieldDefinition = inputs }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = services.GetService(); // context.RequestServices.GetService(); session.Save(ci); await session.SaveChangesAsync(); @@ -196,10 +195,10 @@ public async Task ShouldFilterByContentItemIndexWhenSqlTablePrefixIsUsed() var context = new ResolveFieldContext { Arguments = new Dictionary(), - UserContext = new GraphQLUserContext - { - ServiceProvider = services - }, + //UserContext = new GraphQLUserContext + //{ + // ServiceProvider = services + //}, //ReturnType = returnType, FieldDefinition = inputs }; @@ -207,7 +206,7 @@ public async Task ShouldFilterByContentItemIndexWhenSqlTablePrefixIsUsed() var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); @@ -252,10 +251,10 @@ public async Task ShouldFilterByAliasIndexRegardlessOfInputFieldCase(string fiel var context = new ResolveFieldContext { Arguments = new Dictionary(), - UserContext = new GraphQLUserContext - { - ServiceProvider = services - }, + //UserContext = new GraphQLUserContext + //{ + // ServiceProvider = services + //}, //ReturnType = returnType, FieldDefinition = inputs }; @@ -263,7 +262,7 @@ public async Task ShouldFilterByAliasIndexRegardlessOfInputFieldCase(string fiel var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); @@ -300,17 +299,17 @@ public async Task ShouldBeAbleToUseTheSameIndexForMultipleAliases() var context = new ResolveFieldContext { Arguments = new Dictionary(), - UserContext = new GraphQLUserContext - { - ServiceProvider = services - }, + //UserContext = new GraphQLUserContext + //{ + // ServiceProvider = services + //}, //ReturnType = returnType }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new Animal { Name = "doug" }); - var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); @@ -356,10 +355,10 @@ public async Task ShouldFilterOnMultipleIndexesOnSameAlias() var context = new ResolveFieldContext { Arguments = new Dictionary(), - UserContext = new GraphQLUserContext - { - ServiceProvider = services - }, + //UserContext = new GraphQLUserContext + //{ + // ServiceProvider = services + //}, //ReturnType = returnType }; @@ -372,7 +371,7 @@ public async Task ShouldFilterOnMultipleIndexesOnSameAlias() var ci2 = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "3", ContentItemVersionId = "3" }; ci2.Weld(new Animal { Name = "tommy", IsHappy = false, IsScary = true }); - var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); session.Save(ci1); session.Save(ci2); @@ -415,10 +414,10 @@ public async Task ShouldFilterPartsWithoutAPrefixWhenThePartHasNoPrefix() var context = new ResolveFieldContext { Arguments = new Dictionary(), - UserContext = new GraphQLUserContext - { - ServiceProvider = services - }, + //UserContext = new GraphQLUserContext + //{ + // ServiceProvider = services + //}, //ReturnType = returnType, FieldDefinition = inputs }; @@ -426,7 +425,7 @@ public async Task ShouldFilterPartsWithoutAPrefixWhenThePartHasNoPrefix() var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); @@ -464,10 +463,10 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() var context = new ResolveFieldContext { Arguments = new Dictionary(), - UserContext = new GraphQLUserContext - { - ServiceProvider = services - }, + //UserContext = new GraphQLUserContext + //{ + // ServiceProvider = services + //}, //ReturnType = returnType, FieldDefinition = new FieldType { @@ -487,7 +486,7 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); session.Save(ci); await session.SaveChangesAsync(); diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs index 8aa60a77020..c16e6d554dd 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs @@ -121,7 +121,7 @@ private ExecutionOptions BuildExecutionOptions(string query, PermissionsContext Schema = new ValidationSchema(), UserContext = new GraphQLUserContext { - ServiceProvider = serviceProvider, + //ServiceProvider = serviceProvider, User = new ClaimsPrincipal(new StubIdentity()) }, ValidationRules = DocumentValidator.CoreRules.Concat(serviceProvider.GetServices()) From 58330aa489c37a8cd64868aab040b67365caadd0 Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 10:11:13 +0100 Subject: [PATCH 06/29] fix newtonsoft conflicting reference --- .../OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs index 345d8b367c8..a80066b5c4e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs @@ -2,10 +2,12 @@ using System.IO; using System.Linq; using System.Net; +using System.Net.Mime; using System.Text; using System.Threading.Tasks; using GraphQL; using GraphQL.Execution; +using GQLNS = GraphQL.NewtonsoftJson; using GraphQL.Validation; using GraphQL.Validation.Complexity; using Microsoft.AspNetCore.Authentication; @@ -163,7 +165,7 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic _.Schema = schema; _.Query = queryToExecute; _.OperationName = request.OperationName; - _.Inputs = request.Variables.ToInputs(); + _.Inputs = GQLNS::StringExtensions.ToInputs(request.Variables); _.UserContext = _settings.BuildUserContext?.Invoke(context); _.ExposeExceptions = _settings.ExposeExceptions; _.ValidationRules = DocumentValidator.CoreRules @@ -183,11 +185,10 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic ? HttpStatusCode.Unauthorized : HttpStatusCode.BadRequest); - context.Response.ContentType = "application/json"; + context.Response.ContentType = MediaTypeNames.Application.Json; - // Asynchronous write to the response body is mandatory. - var encodedBytes = _utf8Encoding.GetBytes(JObject.FromObject(result).ToString()); - await context.Response.Body.WriteAsync(encodedBytes, 0, encodedBytes.Length); + var writer = new GQLNS::DocumentWriter(); + await writer.WriteAsync(context.Response.Body, result); } private async Task WriteErrorAsync(HttpContext context, string message, Exception e = null) @@ -212,7 +213,7 @@ private async Task WriteErrorAsync(HttpContext context, string message, Exceptio } context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - context.Response.ContentType = "application/json"; + context.Response.ContentType = MediaTypeNames.Application.Json; // Asynchronous write to the response body is mandatory. var encodedBytes = _utf8Encoding.GetBytes(JObject.FromObject(errorResult).ToString()); From b86b5399cd22b82e511276eae653a95645f5d96a Mon Sep 17 00:00:00 2001 From: Antoine Griffard Date: Fri, 9 Apr 2021 11:57:27 +0200 Subject: [PATCH 07/29] More fixes --- .../OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs | 3 ++- .../OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs index a80066b5c4e..925e3e7f04a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs @@ -167,7 +167,8 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic _.OperationName = request.OperationName; _.Inputs = GQLNS::StringExtensions.ToInputs(request.Variables); _.UserContext = _settings.BuildUserContext?.Invoke(context); - _.ExposeExceptions = _settings.ExposeExceptions; + _.RequestServices = context.RequestServices; + //_.ExposeExceptions = _settings.ExposeExceptions; _.ValidationRules = DocumentValidator.CoreRules .Concat(context.RequestServices.GetServices()); _.ComplexityConfiguration = new ComplexityConfiguration diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs index ac8cdc85024..736627b4132 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.AspNetCore.Http; namespace OrchardCore.Apis.GraphQL @@ -6,7 +7,7 @@ namespace OrchardCore.Apis.GraphQL public class GraphQLSettings { public PathString Path { get; set; } = "/api/graphql"; - public Func BuildUserContext { get; set; } + public Func> BuildUserContext { get; set; } public bool ExposeExceptions { get; set; } = false; From 80c983b6e41dca9bc5a1d414e11b9f1ceba6ec5d Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 11:16:37 +0100 Subject: [PATCH 08/29] improving middleware --- .../Extensions/DocumentWriterExtensions.cs | 39 ++++++ .../GraphQLMiddleware.cs | 120 ++++++++---------- .../RequestServicesDependencyResolver.cs | 38 ------ .../OrchardCore.Apis.GraphQL/Startup.cs | 6 + .../GraphQLSettings.cs | 3 +- .../GraphQLUserContext.cs | 5 +- 6 files changed, 99 insertions(+), 112 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Extensions/DocumentWriterExtensions.cs delete mode 100644 src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/RequestServicesDependencyResolver.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Extensions/DocumentWriterExtensions.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Extensions/DocumentWriterExtensions.cs new file mode 100644 index 00000000000..3da70d976ef --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Extensions/DocumentWriterExtensions.cs @@ -0,0 +1,39 @@ +using System; +using System.Net; +using System.Net.Mime; +using System.Threading.Tasks; +using GraphQL; +using Microsoft.AspNetCore.Http; + +namespace OrchardCore.Apis.GraphQL +{ + internal static class DocumentWriterExtensions + { + public static async Task WriteErrorAsync(this IDocumentWriter documentWriter, HttpContext context, string message, Exception e = null) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + var errorResult = new ExecutionResult + { + Errors = new ExecutionErrors() + }; + + if (e == null) + { + errorResult.Errors.Add(new ExecutionError(message)); + } + else + { + errorResult.Errors.Add(new ExecutionError(message, e)); + } + + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + context.Response.ContentType = MediaTypeNames.Application.Json; + + await documentWriter.WriteAsync(context.Response.Body, errorResult); + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs index a80066b5c4e..a0596cbe4c7 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using GraphQL; using GraphQL.Execution; -using GQLNS = GraphQL.NewtonsoftJson; +using GraphQL.NewtonsoftJson; using GraphQL.Validation; using GraphQL.Validation.Complexity; using Microsoft.AspNetCore.Authentication; @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OrchardCore.Apis.GraphQL.Queries; using OrchardCore.Apis.GraphQL.ValidationRules; @@ -42,7 +43,7 @@ public GraphQLMiddleware( _executer = executer; } - public async Task Invoke(HttpContext context, IAuthorizationService authorizationService, IAuthenticationService authenticationService, ISchemaFactory schemaService) + public async Task Invoke(HttpContext context, IAuthorizationService authorizationService, IAuthenticationService authenticationService, ISchemaFactory schemaService, IDocumentWriter documentWriter) { if (!IsGraphQLRequest(context)) { @@ -61,7 +62,7 @@ public async Task Invoke(HttpContext context, IAuthorizationService authorizatio if (authorized) { - await ExecuteAsync(context, schemaService); + await ExecuteAsync(context, schemaService, documentWriter); } else { @@ -75,7 +76,7 @@ private bool IsGraphQLRequest(HttpContext context) return context.Request.Path.StartsWithNormalizedSegments(_settings.Path, StringComparison.OrdinalIgnoreCase); } - private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaService) + private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaService, IDocumentWriter documentWriter) { var schema = await schemaService.GetSchemaAsync(); @@ -83,66 +84,49 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic // c.f. https://graphql.org/learn/serving-over-http/#post-request - if (HttpMethods.IsPost(context.Request.Method)) + try { - var mediaType = new MediaType(context.Request.ContentType); - - try + if (HttpMethods.IsPost(context.Request.Method)) { - if (mediaType.IsSubsetOf(_jsonMediaType)) - { - using (var sr = new StreamReader(context.Request.Body)) - { - // Asynchronous read is mandatory. - var json = await sr.ReadToEndAsync(); - request = JObject.Parse(json).ToObject(); - } - } - else if (mediaType.IsSubsetOf(_graphQlMediaType)) - { - request = new GraphQLRequest(); + var mediaType = new MediaType(context.Request.ContentType); - using (var sr = new StreamReader(context.Request.Body)) - { - request.Query = await sr.ReadToEndAsync(); - } - } - else if (context.Request.Query.ContainsKey("query")) + if (mediaType.IsSubsetOf(_jsonMediaType) || mediaType.IsSubsetOf(_graphQlMediaType)) { - request = new GraphQLRequest - { - Query = context.Request.Query["query"] - }; + using var sr = new StreamReader(context.Request.Body); - if (context.Request.Query.ContainsKey("variables")) + if (mediaType.IsSubsetOf(_graphQlMediaType)) { - request.Variables = JObject.Parse(context.Request.Query["variables"]); + request = new GraphQLRequest + { + Query = await sr.ReadToEndAsync() + }; } - - if (context.Request.Query.ContainsKey("operationName")) + else { - request.OperationName = context.Request.Query["operationName"]; + using var jsonReader = new JsonTextReader(sr); + var ser = new JsonSerializer(); + request = ser.Deserialize(jsonReader); } } + else + { + request = CreateRequestFromQueryString(context); + } } - catch (Exception e) + else if (HttpMethods.IsGet(context.Request.Method)) { - await WriteErrorAsync(context, "An error occurred while processing the GraphQL query", e); - return; - } - } - else if (HttpMethods.IsGet(context.Request.Method)) - { - if (!context.Request.Query.ContainsKey("query")) - { - await WriteErrorAsync(context, "The 'query' query string parameter is missing"); - return; + request = CreateRequestFromQueryString(context, true); } - request = new GraphQLRequest + if (request == null) { - Query = context.Request.Query["query"] - }; + throw new InvalidOperationException("Unable to create a graphqlrequest from this request"); + } + } + catch (Exception e) + { + await documentWriter.WriteErrorAsync(context, "An error occurred while processing the GraphQL query", e); + return; } var queryToExecute = request.Query; @@ -165,9 +149,8 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic _.Schema = schema; _.Query = queryToExecute; _.OperationName = request.OperationName; - _.Inputs = GQLNS::StringExtensions.ToInputs(request.Variables); + _.Inputs = request.Variables.ToInputs(); _.UserContext = _settings.BuildUserContext?.Invoke(context); - _.ExposeExceptions = _settings.ExposeExceptions; _.ValidationRules = DocumentValidator.CoreRules .Concat(context.RequestServices.GetServices()); _.ComplexityConfiguration = new ComplexityConfiguration @@ -186,38 +169,37 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic : HttpStatusCode.BadRequest); context.Response.ContentType = MediaTypeNames.Application.Json; - - var writer = new GQLNS::DocumentWriter(); - await writer.WriteAsync(context.Response.Body, result); + await documentWriter.WriteAsync(context.Response.Body, result); } - private async Task WriteErrorAsync(HttpContext context, string message, Exception e = null) + private GraphQLRequest CreateRequestFromQueryString(HttpContext context, bool validateQueryKey = false) { - if (message == null) + if (!context.Request.Query.ContainsKey("query")) { - throw new ArgumentNullException(nameof(message)); + if (validateQueryKey) + { + throw new InvalidOperationException("The 'query' query string parameter is missing"); + } + + return null; } - var errorResult = new ExecutionResult + var request = new GraphQLRequest { - Errors = new ExecutionErrors() + Query = context.Request.Query["query"] }; - if (e == null) + if (context.Request.Query.ContainsKey("variables")) { - errorResult.Errors.Add(new ExecutionError(message)); + request.Variables = JObject.Parse(context.Request.Query["variables"]); } - else + + if (context.Request.Query.ContainsKey("operationName")) { - errorResult.Errors.Add(new ExecutionError(message, e)); + request.OperationName = context.Request.Query["operationName"]; } - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - context.Response.ContentType = MediaTypeNames.Application.Json; - - // Asynchronous write to the response body is mandatory. - var encodedBytes = _utf8Encoding.GetBytes(JObject.FromObject(errorResult).ToString()); - await context.Response.Body.WriteAsync(encodedBytes, 0, encodedBytes.Length); + return request; } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/RequestServicesDependencyResolver.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/RequestServicesDependencyResolver.cs deleted file mode 100644 index fa94848263e..00000000000 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/RequestServicesDependencyResolver.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using GraphQL; -using Microsoft.AspNetCore.Http; - -namespace OrchardCore.Apis.GraphQL -{ - ///// - ///// Provides an implementation of by - ///// resolving the HttpContext request services when a type is resolved. - ///// This should be registered as Singleton. - ///// - //internal class RequestServicesDependencyResolver : IDependencyResolver - //{ - // private readonly IHttpContextAccessor _httpContextAccessor; - - // public RequestServicesDependencyResolver(IHttpContextAccessor httpContextAccessor) - // { - // _httpContextAccessor = httpContextAccessor; - // } - - // public T Resolve() - // { - // return (T)Resolve(typeof(T)); - // } - - // public object Resolve(Type type) - // { - // var serviceType = _httpContextAccessor.HttpContext.RequestServices.GetService(type); - - // if (serviceType == null) - // { - // return Activator.CreateInstance(type); - // } - - // return serviceType; - // } - //} -} diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs index 70e8597706d..c6ea831cee9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs @@ -40,6 +40,12 @@ public override void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(services => + { + var settings = services.GetRequiredService>(); + return new ErrorInfoProvider(new ErrorInfoProviderOptions { ExposeExceptionStackTrace = settings.Value.ExposeExceptions }); + }); + services.AddSingleton(); services.AddScoped(); services.AddScoped(); diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs index ac8cdc85024..736627b4132 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLSettings.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.AspNetCore.Http; namespace OrchardCore.Apis.GraphQL @@ -6,7 +7,7 @@ namespace OrchardCore.Apis.GraphQL public class GraphQLSettings { public PathString Path { get; set; } = "/api/graphql"; - public Func BuildUserContext { get; set; } + public Func> BuildUserContext { get; set; } public bool ExposeExceptions { get; set; } = false; diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs index a293c07af02..4843e940be6 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/GraphQLUserContext.cs @@ -1,16 +1,13 @@ -using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading; -using Microsoft.AspNetCore.Http; namespace OrchardCore.Apis.GraphQL { public class GraphQLUserContext : Dictionary { - // public HttpContext HttpContext { get; set; } public ClaimsPrincipal User { get; set; } - // public IServiceProvider ServiceProvider { get; set; } + public SemaphoreSlim ExecutionContextLock { get; } = new SemaphoreSlim(1, 1); } } From 782f5168e74c61b000205a806aca1b91426f83ad Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 14:41:55 +0100 Subject: [PATCH 09/29] actually fixed one test :P --- .../GraphQLMiddleware.cs | 10 +++---- .../OrchardCore.Apis.GraphQL.csproj | 1 + .../Services/SchemaService.cs | 26 ++++++++++--------- .../OrchardCore.Apis.GraphQL/Startup.cs | 2 +- .../Queries/ContentItemsFieldType.cs | 7 ++++- .../GraphQL/ContentItemsFieldTypeTests.cs | 19 +++++--------- 6 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs index 95952c370ac..951683cc2ba 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs @@ -78,7 +78,7 @@ private bool IsGraphQLRequest(HttpContext context) private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaService, IDocumentWriter documentWriter) { - var schema = await schemaService.GetSchemaAsync(); + GraphQLRequest request = null; @@ -103,9 +103,8 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic } else { - using var jsonReader = new JsonTextReader(sr); - var ser = new JsonSerializer(); - request = ser.Deserialize(jsonReader); + var json = await sr.ReadToEndAsync(); + request = JObject.Parse(json).ToObject(); } } else @@ -142,6 +141,7 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic queryToExecute = queries[request.NamedQuery]; } + var schema = await schemaService.GetSchemaAsync(); var dataLoaderDocumentListener = context.RequestServices.GetRequiredService(); var result = await _executer.ExecuteAsync(_ => @@ -173,7 +173,7 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic await documentWriter.WriteAsync(context.Response.Body, result); } - private GraphQLRequest CreateRequestFromQueryString(HttpContext context, bool validateQueryKey = false) + private static GraphQLRequest CreateRequestFromQueryString(HttpContext context, bool validateQueryKey = false) { if (!context.Request.Query.ContainsKey("query")) { diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj index 2980984fa04..c9b15fd031a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj @@ -16,6 +16,7 @@ + diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs index 09f9ad17bf8..eb7d9605927 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using GraphQL; +using GraphQL.MicrosoftDI; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using OrchardCore.Environment.Shell.Scope; @@ -62,14 +63,24 @@ public async Task GetSchemaAsync() var serviceProvider = ShellScope.Services; - var schema = new Schema + var schema = new Schema(new SelfActivatingServiceProvider(serviceProvider)) { Query = new ObjectGraphType { Name = "Query" }, Mutation = new ObjectGraphType { Name = "Mutation" }, Subscription = new ObjectGraphType { Name = "Subscription" }, - NameConverter = new OrchardFieldNameConverter() + NameConverter = new OrchardFieldNameConverter(), }; + foreach (var type in serviceProvider.GetServices()) + { + schema.RegisterType(type); + } + + foreach (var type in serviceProvider.GetServices()) + { + schema.RegisterType(type); + } + foreach (var builder in _schemaBuilders) { var identifier = await builder.GetIdentifierAsync(); @@ -83,16 +94,7 @@ public async Task GetSchemaAsync() await builder.BuildAsync(schema); } - foreach (var type in serviceProvider.GetServices()) - { - schema.RegisterType(type); - } - - foreach (var type in serviceProvider.GetServices()) - { - schema.RegisterType(type); - } - + // Clean Query, Mutation and Subscription if they have no fields // to prevent GraphQL configuration errors. diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs index c6ea831cee9..e82df391eb6 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs @@ -6,6 +6,7 @@ using GraphQL.Validation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -35,7 +36,6 @@ public Startup(IOptions adminOptions, IHostEnvironment hostingEnvi public override void ConfigureServices(IServiceCollection services) { - //services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index 8baf7c49ea7..21a1c73d0c1 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -77,7 +77,12 @@ private async Task> Resolve(IResolveFieldContext contex JObject where = null; if (context.HasArgument("where")) { - where = JObject.FromObject(context.Arguments["where"]); + var whereArgument = context.Arguments["where"]; + + if (whereArgument.Value != null) + { + where = JObject.FromObject(whereArgument.Value); + } } var session = context.RequestServices.GetService(); diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index 508710ee5c7..f731a8dba72 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -132,32 +132,25 @@ public async Task ShouldFilterByContentItemIndex() services.Services.AddSingleton>(); services.Build(); - var returnType = new ListGraphType(); - returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; - var animalWhereInput = new AnimalPartWhereInput(); - var inputs = new FieldType { Name = "Inputs", Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; - - //var a = new GraphQLUserContext - //{ - // ServiceProvider = services - //}; + var inputs = new FieldType { Name = "Inputs", ResolvedType = new StringGraphType() { Name = "Animal" }, Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; var context = new ResolveFieldContext { Arguments = new Dictionary(), - UserContext = null, - FieldDefinition = inputs + UserContext = new GraphQLUserContext(), + FieldDefinition = inputs, + RequestServices = services }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = services.GetService(); // context.RequestServices.GetService(); + var session = context.RequestServices.GetService(); session.Save(ci); await session.SaveChangesAsync(); - var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); + var type = new ContentItemsFieldType("Animal", new Schema(services), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); context.Arguments["where"] = new ArgumentValue(JObject.Parse("{ contentItemId: \"1\" }"), ArgumentSource.Variable); var dogs = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); From 5851eb1842960bdcc6c45683609086679546b7e1 Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 15:01:58 +0100 Subject: [PATCH 10/29] fixed some more tests --- .../Queries/ContentItemsFieldType.cs | 1 - .../GraphQL/ContentItemsFieldTypeTests.cs | 76 +++++++------------ 2 files changed, 26 insertions(+), 51 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index 21a1c73d0c1..accaec8a6c8 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -226,7 +226,6 @@ private static IQuery FilterContentType(IQuery q.ContentType == contentType); diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index f731a8dba72..ea833eda0bd 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -83,7 +83,7 @@ public Task DisposeAsync() return Task.CompletedTask; } - private async Task CreateTablesAsync(IStore store) + private static async Task CreateTablesAsync(IStore store) { using (var session = store.CreateSession()) { @@ -179,31 +179,25 @@ public async Task ShouldFilterByContentItemIndexWhenSqlTablePrefixIsUsed() services.Services.AddScoped(x => shellSettings); services.Build(); - var returnType = new ListGraphType(); - returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; - var animalWhereInput = new AnimalPartWhereInput(); - var inputs = new FieldType { Name = "Inputs", Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; + var inputs = new FieldType { Name = "Inputs", ResolvedType = new StringGraphType() { Name = "Animal" }, Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; var context = new ResolveFieldContext { Arguments = new Dictionary(), - //UserContext = new GraphQLUserContext - //{ - // ServiceProvider = services - //}, - //ReturnType = returnType, - FieldDefinition = inputs + UserContext = new GraphQLUserContext(), + FieldDefinition = inputs, + RequestServices = services }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = context.RequestServices.GetService(); session.Save(ci); await session.SaveChangesAsync(); - var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); + var type = new ContentItemsFieldType("Animal", new Schema(services), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); context.Arguments["where"] = new ArgumentValue(JObject.Parse("{ contentItemId: \"1\" }"), ArgumentSource.Variable); var dogs = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); @@ -232,30 +226,22 @@ public async Task ShouldFilterByAliasIndexRegardlessOfInputFieldCase(string fiel services.Services.AddSingleton>(); services.Build(); - var returnType = new ListGraphType - { - ResolvedType = new StringGraphType() { Name = "Animal" } - }; - // setup the whereinput fieldname with the test data var animalWhereInput = new AnimalPartWhereInput(fieldName); - var inputs = new FieldType { Name = "Inputs", Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; + var inputs = new FieldType { Name = "Inputs", ResolvedType = new StringGraphType() { Name = "Animal" }, Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; var context = new ResolveFieldContext { Arguments = new Dictionary(), - //UserContext = new GraphQLUserContext - //{ - // ServiceProvider = services - //}, - //ReturnType = returnType, - FieldDefinition = inputs + UserContext = new GraphQLUserContext(), + FieldDefinition = inputs, + RequestServices = services }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = context.RequestServices.GetService(); session.Save(ci); await session.SaveChangesAsync(); @@ -286,23 +272,21 @@ public async Task ShouldBeAbleToUseTheSameIndexForMultipleAliases() services.Build(); - var returnType = new ListGraphType(); - returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; + //var returnType = new ListGraphType(); + //returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; var context = new ResolveFieldContext { Arguments = new Dictionary(), - //UserContext = new GraphQLUserContext - //{ - // ServiceProvider = services - //}, - //ReturnType = returnType + UserContext = new GraphQLUserContext(), + FieldDefinition = new FieldType { ResolvedType = new StringGraphType() { Name = "Animal" } }, + RequestServices = services }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new Animal { Name = "doug" }); - var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = context.RequestServices.GetService(); session.Save(ci); await session.SaveChangesAsync(); @@ -398,27 +382,22 @@ public async Task ShouldFilterPartsWithoutAPrefixWhenThePartHasNoPrefix() services.Services.AddSingleton>(); services.Build(); - var returnType = new ListGraphType(); - returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; var animalWhereInput = new AnimalPartWhereInput(); - var inputs = new FieldType { Name = "Inputs", Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; + var inputs = new FieldType { Name = "Inputs", ResolvedType = new StringGraphType() { Name = "Animal" }, Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; var context = new ResolveFieldContext { Arguments = new Dictionary(), - //UserContext = new GraphQLUserContext - //{ - // ServiceProvider = services - //}, - //ReturnType = returnType, - FieldDefinition = inputs + UserContext = new GraphQLUserContext(), + FieldDefinition = inputs, + RequestServices = services }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = context.RequestServices.GetService(); session.Save(ci); await session.SaveChangesAsync(); @@ -456,11 +435,8 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() var context = new ResolveFieldContext { Arguments = new Dictionary(), - //UserContext = new GraphQLUserContext - //{ - // ServiceProvider = services - //}, - //ReturnType = returnType, + UserContext = new GraphQLUserContext(), + RequestServices = services, FieldDefinition = new FieldType { Name = "Inputs", @@ -479,7 +455,7 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = context.RequestServices.GetService(); session.Save(ci); await session.SaveChangesAsync(); From ee07c35d0254b3c3aa35b0defebbad018f6cb907 Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 15:16:48 +0100 Subject: [PATCH 11/29] . --- .../Apis/GraphQL/ContentItemsFieldTypeTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index ea833eda0bd..61ef7159d11 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -427,9 +427,6 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() services.Services.AddSingleton>(); services.Build(); - var returnType = new ListGraphType(); - returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; - var animalWhereInput = new AnimalPartCollapsedWhereInput(); var context = new ResolveFieldContext @@ -440,6 +437,7 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() FieldDefinition = new FieldType { Name = "Inputs", + ResolvedType = new StringGraphType() { Name = "Animal" }, Arguments = new QueryArguments { new QueryArgument From 1501f381680d80084b5330b53e5a906d780dccc4 Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 16:45:27 +0100 Subject: [PATCH 12/29] fixed some of the blog integration tests --- .../ResolveFieldContextExtensions.cs | 4 +-- .../Queries/ContentItemsFieldType.cs | 31 +++++++++---------- .../Apis/GraphQL/Blog/BlogPostTests.cs | 1 + 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs index b19beb033c8..c33124a5abc 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs @@ -15,7 +15,7 @@ public static bool HasPopulatedArgument(this IResolveFieldContext source, string { if (source.Arguments?.ContainsKey(argumentName) ?? false) { - return !string.IsNullOrEmpty(source.Arguments[argumentName].ToString()); + return !string.IsNullOrEmpty((source.Arguments[argumentName].Value ?? string.Empty).ToString()); }; return false; @@ -25,7 +25,7 @@ public static bool HasPopulatedArgument(this IResolveFieldContext FilterContentType(IQuery q.ContentType == contentType); } @@ -281,22 +280,22 @@ private void BuildExpressionsInternal(JObject where, Junction expressions, strin // figure out table aliases for collapsed parts and ones with the part suffix removed by the dsl if (tableAlias == null || !tableAlias.EndsWith("Part", StringComparison.OrdinalIgnoreCase)) { - var whereArgument = fieldContext?.FieldDefinition?.Arguments.FirstOrDefault(x => x.Name == "where"); + var whereArgument = fieldContext?.Arguments.FirstOrDefault(x => x.Key == "where"); if (whereArgument != null) { - var whereInput = (WhereInputObjectGraphType)whereArgument.ResolvedType; - - foreach (var field in whereInput.Fields.Where(x => x.GetMetadata("PartName") != null)) - { - var partName = field.GetMetadata("PartName"); - if ((tableAlias == null && field.GetMetadata("PartCollapsed") && field.Name.Equals(property, StringComparison.OrdinalIgnoreCase)) || - (tableAlias != null && partName.ToFieldName().Equals(tableAlias, StringComparison.OrdinalIgnoreCase))) - { - tableAlias = indexAliases.TryGetValue(partName, out var indexTableAlias) ? indexTableAlias : tableAlias; - break; - } - } + //var whereInput = (WhereInputObjectGraphType)whereArgument..ResolvedType; + + //foreach (var field in whereInput.Fields.Where(x => x.GetMetadata("PartName") != null)) + //{ + // var partName = field.GetMetadata("PartName"); + // if ((tableAlias == null && field.GetMetadata("PartCollapsed") && field.Name.Equals(property, StringComparison.OrdinalIgnoreCase)) || + // (tableAlias != null && partName.ToFieldName().Equals(tableAlias, StringComparison.OrdinalIgnoreCase))) + // { + // tableAlias = indexAliases.TryGetValue(partName, out var indexTableAlias) ? indexTableAlias : tableAlias; + // break; + // } + //} } } @@ -371,7 +370,7 @@ private IQuery OrderBy(IQuery Date: Fri, 9 Apr 2021 17:12:04 +0100 Subject: [PATCH 13/29] fix some tests --- .../Queries/ContentItemsFieldType.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index 5985e1d2a77..e711bece543 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -280,22 +280,22 @@ private void BuildExpressionsInternal(JObject where, Junction expressions, strin // figure out table aliases for collapsed parts and ones with the part suffix removed by the dsl if (tableAlias == null || !tableAlias.EndsWith("Part", StringComparison.OrdinalIgnoreCase)) { - var whereArgument = fieldContext?.Arguments.FirstOrDefault(x => x.Key == "where"); + var whereArgument = fieldContext?.FieldDefinition.Arguments.FirstOrDefault(x => x.Name == "where"); if (whereArgument != null) { - //var whereInput = (WhereInputObjectGraphType)whereArgument..ResolvedType; - - //foreach (var field in whereInput.Fields.Where(x => x.GetMetadata("PartName") != null)) - //{ - // var partName = field.GetMetadata("PartName"); - // if ((tableAlias == null && field.GetMetadata("PartCollapsed") && field.Name.Equals(property, StringComparison.OrdinalIgnoreCase)) || - // (tableAlias != null && partName.ToFieldName().Equals(tableAlias, StringComparison.OrdinalIgnoreCase))) - // { - // tableAlias = indexAliases.TryGetValue(partName, out var indexTableAlias) ? indexTableAlias : tableAlias; - // break; - // } - //} + var whereInput = (WhereInputObjectGraphType)whereArgument.ResolvedType; + + foreach (var field in whereInput.Fields.Where(x => x.GetMetadata("PartName") != null)) + { + var partName = field.GetMetadata("PartName"); + if ((tableAlias == null && field.GetMetadata("PartCollapsed") && field.Name.Equals(property, StringComparison.OrdinalIgnoreCase)) || + (tableAlias != null && partName.ToFieldName().Equals(tableAlias, StringComparison.OrdinalIgnoreCase))) + { + tableAlias = indexAliases.TryGetValue(partName, out var indexTableAlias) ? indexTableAlias : tableAlias; + break; + } + } } } From c97640f56425aebef5af551cc0bb8f74e36333d8 Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Fri, 9 Apr 2021 17:46:35 +0100 Subject: [PATCH 14/29] fixed more tests --- .../GraphQL/ContentItemsFieldTypeTests.cs | 79 ++++++++++++++----- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index 61ef7159d11..acf149b4871 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -133,13 +133,23 @@ public async Task ShouldFilterByContentItemIndex() services.Build(); var animalWhereInput = new AnimalPartWhereInput(); - var inputs = new FieldType { Name = "Inputs", ResolvedType = new StringGraphType() { Name = "Animal" }, Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; var context = new ResolveFieldContext { Arguments = new Dictionary(), UserContext = new GraphQLUserContext(), - FieldDefinition = inputs, + FieldDefinition = new FieldType + { + Name = "Inputs", + ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), + Arguments = new QueryArguments { + new QueryArgument { + Name = "where", + Description = "filters the animals", + ResolvedType = animalWhereInput + } + } + }, RequestServices = services }; @@ -180,13 +190,23 @@ public async Task ShouldFilterByContentItemIndexWhenSqlTablePrefixIsUsed() services.Build(); var animalWhereInput = new AnimalPartWhereInput(); - var inputs = new FieldType { Name = "Inputs", ResolvedType = new StringGraphType() { Name = "Animal" }, Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; - + var context = new ResolveFieldContext { Arguments = new Dictionary(), UserContext = new GraphQLUserContext(), - FieldDefinition = inputs, + FieldDefinition = new FieldType + { + Name = "Inputs", + ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), + Arguments = new QueryArguments { + new QueryArgument { + Name = "where", + Description = "filters the animals", + ResolvedType = animalWhereInput + } + } + }, RequestServices = services }; @@ -228,13 +248,22 @@ public async Task ShouldFilterByAliasIndexRegardlessOfInputFieldCase(string fiel // setup the whereinput fieldname with the test data var animalWhereInput = new AnimalPartWhereInput(fieldName); - var inputs = new FieldType { Name = "Inputs", ResolvedType = new StringGraphType() { Name = "Animal" }, Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; var context = new ResolveFieldContext { Arguments = new Dictionary(), + FieldDefinition = new FieldType { + Name = "Inputs", + ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), + Arguments = new QueryArguments { + new QueryArgument { + Name = "where", + Description = "filters the animals", + ResolvedType = animalWhereInput + } + } + }, UserContext = new GraphQLUserContext(), - FieldDefinition = inputs, RequestServices = services }; @@ -245,7 +274,7 @@ public async Task ShouldFilterByAliasIndexRegardlessOfInputFieldCase(string fiel session.Save(ci); await session.SaveChangesAsync(); - var type = new ContentItemsFieldType("Animal", new Schema(), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); + var type = new ContentItemsFieldType("Animal", new Schema(services), Options.Create(new GraphQLContentOptions()), Options.Create(new GraphQLSettings { DefaultNumberOfResults = 10 })); context.Arguments["where"] = new ArgumentValue(JObject.Parse(string.Concat("{ ", fieldName, ": { name: \"doug\" } }")), ArgumentSource.Variable); var dogs = await ((LockedAsyncFieldResolver>)type.Resolver).Resolve(context); @@ -279,7 +308,10 @@ public async Task ShouldBeAbleToUseTheSameIndexForMultipleAliases() { Arguments = new Dictionary(), UserContext = new GraphQLUserContext(), - FieldDefinition = new FieldType { ResolvedType = new StringGraphType() { Name = "Animal" } }, + FieldDefinition = new FieldType + { + ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }) + }, RequestServices = services }; @@ -326,17 +358,15 @@ public async Task ShouldFilterOnMultipleIndexesOnSameAlias() services.Services.AddSingleton>(); services.Build(); - var returnType = new ListGraphType(); - returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; - var context = new ResolveFieldContext { Arguments = new Dictionary(), - //UserContext = new GraphQLUserContext - //{ - // ServiceProvider = services - //}, - //ReturnType = returnType + UserContext = new GraphQLUserContext(), + FieldDefinition = new FieldType + { + ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }) + }, + RequestServices = services }; var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; @@ -382,15 +412,24 @@ public async Task ShouldFilterPartsWithoutAPrefixWhenThePartHasNoPrefix() services.Services.AddSingleton>(); services.Build(); - var animalWhereInput = new AnimalPartWhereInput(); - var inputs = new FieldType { Name = "Inputs", ResolvedType = new StringGraphType() { Name = "Animal" }, Arguments = new QueryArguments { new QueryArgument { Name = "where", Description = "filters the animals", ResolvedType = animalWhereInput } } }; var context = new ResolveFieldContext { Arguments = new Dictionary(), UserContext = new GraphQLUserContext(), - FieldDefinition = inputs, + FieldDefinition = new FieldType + { + Name = "Inputs", + ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), + Arguments = new QueryArguments { + new QueryArgument { + Name = "where", + Description = "filters the animals", + ResolvedType = animalWhereInput + } + } + }, RequestServices = services }; From 257dc55bcf1b52fdd928e969f6f6e14a8d389b83 Mon Sep 17 00:00:00 2001 From: Antoine Griffard Date: Fri, 9 Apr 2021 19:17:58 +0200 Subject: [PATCH 15/29] ResolvedType --- .../Queries/ContentItemsFieldType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index e711bece543..408c977361d 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -226,7 +226,7 @@ private static IQuery FilterContentType(IQuery q.ContentType == contentType); } From 78d0b52a3a0e853450c411b6d85876ef77aca0c9 Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Sat, 10 Apr 2021 00:30:29 +0100 Subject: [PATCH 16/29] fix unit tests --- .../Queries/ContentItemsFieldType.cs | 2 +- .../GraphQL/ContentItemsFieldTypeTests.cs | 183 +++++------------- 2 files changed, 46 insertions(+), 139 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index e711bece543..dbf2e50b09a 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -226,7 +226,7 @@ private static IQuery FilterContentType(IQuery q.ContentType == contentType); } diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index acf149b4871..2a8fea393f5 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -132,26 +132,7 @@ public async Task ShouldFilterByContentItemIndex() services.Services.AddSingleton>(); services.Build(); - var animalWhereInput = new AnimalPartWhereInput(); - - var context = new ResolveFieldContext - { - Arguments = new Dictionary(), - UserContext = new GraphQLUserContext(), - FieldDefinition = new FieldType - { - Name = "Inputs", - ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), - Arguments = new QueryArguments { - new QueryArgument { - Name = "where", - Description = "filters the animals", - ResolvedType = animalWhereInput - } - } - }, - RequestServices = services - }; + var context = CreateAnimalFieldContext(services); var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); @@ -189,26 +170,7 @@ public async Task ShouldFilterByContentItemIndexWhenSqlTablePrefixIsUsed() services.Services.AddScoped(x => shellSettings); services.Build(); - var animalWhereInput = new AnimalPartWhereInput(); - - var context = new ResolveFieldContext - { - Arguments = new Dictionary(), - UserContext = new GraphQLUserContext(), - FieldDefinition = new FieldType - { - Name = "Inputs", - ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), - Arguments = new QueryArguments { - new QueryArgument { - Name = "where", - Description = "filters the animals", - ResolvedType = animalWhereInput - } - } - }, - RequestServices = services - }; + var context = CreateAnimalFieldContext(services); var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); @@ -246,26 +208,7 @@ public async Task ShouldFilterByAliasIndexRegardlessOfInputFieldCase(string fiel services.Services.AddSingleton>(); services.Build(); - // setup the whereinput fieldname with the test data - var animalWhereInput = new AnimalPartWhereInput(fieldName); - - var context = new ResolveFieldContext - { - Arguments = new Dictionary(), - FieldDefinition = new FieldType { - Name = "Inputs", - ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), - Arguments = new QueryArguments { - new QueryArgument { - Name = "where", - Description = "filters the animals", - ResolvedType = animalWhereInput - } - } - }, - UserContext = new GraphQLUserContext(), - RequestServices = services - }; + var context = CreateAnimalFieldContext(services, fieldName); var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); @@ -300,20 +243,8 @@ public async Task ShouldBeAbleToUseTheSameIndexForMultipleAliases() services.Services.AddSingleton>(); services.Build(); - - //var returnType = new ListGraphType(); - //returnType.ResolvedType = new StringGraphType() { Name = "Animal" }; - - var context = new ResolveFieldContext - { - Arguments = new Dictionary(), - UserContext = new GraphQLUserContext(), - FieldDefinition = new FieldType - { - ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }) - }, - RequestServices = services - }; + + var context = CreateAnimalFieldContext(services); var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new Animal { Name = "doug" }); @@ -358,16 +289,7 @@ public async Task ShouldFilterOnMultipleIndexesOnSameAlias() services.Services.AddSingleton>(); services.Build(); - var context = new ResolveFieldContext - { - Arguments = new Dictionary(), - UserContext = new GraphQLUserContext(), - FieldDefinition = new FieldType - { - ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }) - }, - RequestServices = services - }; + var context = CreateAnimalFieldContext(services); var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new Animal { Name = "doug", IsHappy = true, IsScary = false }); @@ -378,7 +300,7 @@ public async Task ShouldFilterOnMultipleIndexesOnSameAlias() var ci2 = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "3", ContentItemVersionId = "3" }; ci2.Weld(new Animal { Name = "tommy", IsHappy = false, IsScary = true }); - var session = services.GetService(); // ((GraphQLUserContext)context.UserContext).ServiceProvider.GetService(); + var session = context.RequestServices.GetService(); session.Save(ci); session.Save(ci1); session.Save(ci2); @@ -412,31 +334,12 @@ public async Task ShouldFilterPartsWithoutAPrefixWhenThePartHasNoPrefix() services.Services.AddSingleton>(); services.Build(); - var animalWhereInput = new AnimalPartWhereInput(); - - var context = new ResolveFieldContext - { - Arguments = new Dictionary(), - UserContext = new GraphQLUserContext(), - FieldDefinition = new FieldType - { - Name = "Inputs", - ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), - Arguments = new QueryArguments { - new QueryArgument { - Name = "where", - Description = "filters the animals", - ResolvedType = animalWhereInput - } - } - }, - RequestServices = services - }; + var context = CreateAnimalFieldContext(services); var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); - var session = context.RequestServices.GetService(); + var session = context.RequestServices.GetService(); session.Save(ci); await session.SaveChangesAsync(); @@ -466,28 +369,7 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() services.Services.AddSingleton>(); services.Build(); - var animalWhereInput = new AnimalPartCollapsedWhereInput(); - - var context = new ResolveFieldContext - { - Arguments = new Dictionary(), - UserContext = new GraphQLUserContext(), - RequestServices = services, - FieldDefinition = new FieldType - { - Name = "Inputs", - ResolvedType = new StringGraphType() { Name = "Animal" }, - Arguments = new QueryArguments - { - new QueryArgument - { - Name = "where", - Description = "filters the animals", - ResolvedType = animalWhereInput - } - } - } - }; + var context = CreateAnimalFieldContext(services, collapsed: true); var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; ci.Weld(new AnimalPart { Name = "doug" }); @@ -505,19 +387,44 @@ public async Task ShouldFilterByCollapsedWhereInputForCollapsedParts() Assert.Equal("doug", dogs.First().As().Name); } } - } - public class AnimalPartWhereInput : WhereInputObjectGraphType - { - public AnimalPartWhereInput() + + private static IResolveFieldContext CreateAnimalFieldContext(IServiceProvider services, string fieldName = null, bool collapsed = false) { - Name = "Test"; - Description = "Foo"; - var fieldType = new FieldType { Name = "Animal", Type = typeof(StringGraphType) }; - fieldType.Metadata["PartName"] = "AnimalPart"; - AddField(fieldType); + IGraphType where; + + if (!collapsed) + { + where = new AnimalPartWhereInput(fieldName ?? "Animal"); + } + else + { + where = new AnimalPartCollapsedWhereInput(); + } + + return new ResolveFieldContext + { + Arguments = new Dictionary(), + UserContext = new GraphQLUserContext(), + FieldDefinition = new FieldType + { + Name = "Inputs", + ResolvedType = new ListGraphType(new StringGraphType() { Name = "Animal" }), + Arguments = new QueryArguments { + new QueryArgument { + Name = "where", + Description = "filters the animals", + ResolvedType = where + } + } + }, + RequestServices = services + }; } + } + public class AnimalPartWhereInput : WhereInputObjectGraphType + { public AnimalPartWhereInput(string fieldName) { Name = "Test"; @@ -536,7 +443,7 @@ public AnimalPartCollapsedWhereInput() Description = "Foo"; var fieldType = new FieldType { Name = "Name", Type = typeof(StringGraphType) }; fieldType.Metadata["PartName"] = "AnimalPart"; - fieldType.Metadata["PartCollapse"] = true; + fieldType.Metadata["PartCollapsed"] = true; AddField(fieldType); } } From 2064734306db7d52c2da0e628506aca58713dacd Mon Sep 17 00:00:00 2001 From: Antoine Griffard Date: Tue, 20 Apr 2021 17:10:31 +0200 Subject: [PATCH 17/29] GraphQL 4.3.0 --- src/OrchardCore.Build/Dependencies.props | 6 +++--- src/docs/resources/libraries/README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index 908d4fe5ee8..3e74389ee12 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -17,9 +17,9 @@ - - - + + + diff --git a/src/docs/resources/libraries/README.md b/src/docs/resources/libraries/README.md index 97b8cf0db81..76e09ea6c2e 100644 --- a/src/docs/resources/libraries/README.md +++ b/src/docs/resources/libraries/README.md @@ -8,7 +8,7 @@ The below table lists the different .NET libraries used in Orchard Core: | [Azure Storage Blobs for DataProtection](https://github.com/Azure/azure-sdk-for-net/blob/Azure.Extensions.AspNetCore.DataProtection.Blobs_1.2.0/sdk/extensions/Azure.Extensions.AspNetCore.DataProtection.Blobs/README.md) | Allows storing ASP.NET Core DataProtection keys in Azure Blob Storage | 1.2.0 |[MIT](https://github.com/Azure/azure-sdk-for-net/blob/master/LICENSE.txt) | | [Castle.Core](https://github.com/castleproject/Core) | Castle DynamicProxy. | 4.4.1 |[Apache-2.0](https://github.com/castleproject/Core/blob/master/LICENSE) | | [Fluid.Core](https://github.com/sebastienros/fluid) | .NET Liquid template engine. | 2.0.5 | [MIT](https://github.com/sebastienros/fluid/blob/dev/LICENSE) | -| [GraphQL](https://github.com/graphql/graphiql) | GraphiQL & GraphQL. | 2.4.0 | [MIT](https://github.com/graphql/graphiql/blob/main/LICENSE) | +| [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) | GraphQL. | 4.3.0 | [MIT](https://github.com/graphql-dotnet/graphql-dotnet/blob/master/LICENSE.md) | | [HtmlSanitizer](https://github.com/mganss/HtmlSanitizer) | Cleans HTML to avoid XSS attacks. | 5.0.376 | [MIT](https://github.com/mganss/HtmlSanitizer/blob/master/LICENSE.md) | | [Image Sharp](https://github.com/SixLabors/ImageSharp.Web) | Middleware for ASP.NET-Core for image manipulation. | 1.0.2 |[Apache-2.0](https://github.com/SixLabors/ImageSharp.Web/blob/master/LICENSE) | | [Irony.Core](https://github.com/daxnet/irony) | A modified version of the Irony project with .NET Core support | 1.0.7 | [MIT](https://github.com/daxnet/irony/blob/master/LICENSE) | From a7fde442fd31d215a1c21c774afa2566f64313d1 Mon Sep 17 00:00:00 2001 From: Antoine Griffard Date: Thu, 22 Apr 2021 10:39:14 +0200 Subject: [PATCH 18/29] GraphQL 4.4.0 --- src/OrchardCore.Build/Dependencies.props | 6 +++--- src/docs/resources/libraries/README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index 3e74389ee12..df164131abd 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -17,9 +17,9 @@ - - - + + + diff --git a/src/docs/resources/libraries/README.md b/src/docs/resources/libraries/README.md index 76e09ea6c2e..912a5e4d79e 100644 --- a/src/docs/resources/libraries/README.md +++ b/src/docs/resources/libraries/README.md @@ -8,7 +8,7 @@ The below table lists the different .NET libraries used in Orchard Core: | [Azure Storage Blobs for DataProtection](https://github.com/Azure/azure-sdk-for-net/blob/Azure.Extensions.AspNetCore.DataProtection.Blobs_1.2.0/sdk/extensions/Azure.Extensions.AspNetCore.DataProtection.Blobs/README.md) | Allows storing ASP.NET Core DataProtection keys in Azure Blob Storage | 1.2.0 |[MIT](https://github.com/Azure/azure-sdk-for-net/blob/master/LICENSE.txt) | | [Castle.Core](https://github.com/castleproject/Core) | Castle DynamicProxy. | 4.4.1 |[Apache-2.0](https://github.com/castleproject/Core/blob/master/LICENSE) | | [Fluid.Core](https://github.com/sebastienros/fluid) | .NET Liquid template engine. | 2.0.5 | [MIT](https://github.com/sebastienros/fluid/blob/dev/LICENSE) | -| [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) | GraphQL. | 4.3.0 | [MIT](https://github.com/graphql-dotnet/graphql-dotnet/blob/master/LICENSE.md) | +| [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) | GraphQL for .NET. | 4.4.0 | [MIT](https://github.com/graphql-dotnet/graphql-dotnet/blob/master/LICENSE.md) | | [HtmlSanitizer](https://github.com/mganss/HtmlSanitizer) | Cleans HTML to avoid XSS attacks. | 5.0.376 | [MIT](https://github.com/mganss/HtmlSanitizer/blob/master/LICENSE.md) | | [Image Sharp](https://github.com/SixLabors/ImageSharp.Web) | Middleware for ASP.NET-Core for image manipulation. | 1.0.2 |[Apache-2.0](https://github.com/SixLabors/ImageSharp.Web/blob/master/LICENSE) | | [Irony.Core](https://github.com/daxnet/irony) | A modified version of the Irony project with .NET Core support | 1.0.7 | [MIT](https://github.com/daxnet/irony/blob/master/LICENSE) | From 1757f542233fc79c938b27510069740b0e0713b6 Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Mon, 11 Oct 2021 17:08:22 +0100 Subject: [PATCH 19/29] graphql 4.6.1 --- src/OrchardCore.Build/Dependencies.props | 6 +++--- src/docs/resources/libraries/README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index 3f3a2d8bb52..58f88edf388 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -17,9 +17,9 @@ - - - + + + diff --git a/src/docs/resources/libraries/README.md b/src/docs/resources/libraries/README.md index 82756332098..8dc167014b3 100644 --- a/src/docs/resources/libraries/README.md +++ b/src/docs/resources/libraries/README.md @@ -9,7 +9,7 @@ The below table lists the different .NET libraries used in Orchard Core: | [Azure Storage Blobs for DataProtection](https://github.com/Azure/azure-sdk-for-net/blob/Azure.Extensions.AspNetCore.DataProtection.Blobs_1.2.0/sdk/extensions/Azure.Extensions.AspNetCore.DataProtection.Blobs/README.md) | Allows storing ASP.NET Core DataProtection keys in Azure Blob Storage | 1.2.0 |[MIT](https://github.com/Azure/azure-sdk-for-net/blob/master/LICENSE.txt) | | [Castle.Core](https://github.com/castleproject/Core) | Castle DynamicProxy. | 4.4.1 |[Apache-2.0](https://github.com/castleproject/Core/blob/master/LICENSE) | | [Fluid.Core](https://github.com/sebastienros/fluid) | .NET Liquid template engine. | 2.0.9 | [MIT](https://github.com/sebastienros/fluid/blob/dev/LICENSE) | -| [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) | GraphQL for .NET. | 4.5.0 | [MIT](https://github.com/graphql-dotnet/graphql-dotnet/blob/master/LICENSE.md) | +| [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) | GraphQL for .NET. | 4.6.1 | [MIT](https://github.com/graphql-dotnet/graphql-dotnet/blob/master/LICENSE.md) | | [HtmlSanitizer](https://github.com/mganss/HtmlSanitizer) | Cleans HTML to avoid XSS attacks. | 5.0.404 | [MIT](https://github.com/mganss/HtmlSanitizer/blob/master/LICENSE.md) | | [Image Sharp](https://github.com/SixLabors/ImageSharp.Web) | Middleware for ASP.NET-Core for image manipulation. | 1.0.2 |[Apache-2.0](https://github.com/SixLabors/ImageSharp.Web/blob/master/LICENSE) | | [Irony.Core](https://github.com/daxnet/irony) | A modified version of the Irony project with .NET Core support | 1.0.7 | [MIT](https://github.com/daxnet/irony/blob/master/LICENSE) | From 48bfb93f9f52dbb78f4948995dd1377dca3a5d33 Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Mon, 11 Oct 2021 21:47:42 +0100 Subject: [PATCH 20/29] microsoftdi package ref issue --- src/OrchardCore.Build/Dependencies.props | 1 + .../OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj | 2 +- .../GraphQL/Types/ContentPickerFieldQueryObjectType.cs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index bcbc6562572..ad01d379bea 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -19,6 +19,7 @@ + diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj index c9b15fd031a..92a5d7faa25 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs index 5edd4485a28..b2969b3a73c 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/ContentPickerFieldQueryObjectType.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using GraphQL.DataLoader; using GraphQL.Types; using OrchardCore.Apis.GraphQL; From c68e6ba43bbd69db941a1915b649b7fcc8f2ffcf Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Tue, 12 Oct 2021 00:43:07 +0100 Subject: [PATCH 21/29] fix permissions --- .../RequiresPermissionValidationRule.cs | 145 ++++++++++++------ .../Extensions/PermissionsExtensions.cs | 15 +- .../RequiresPermissionValidationRuleTests.cs | 19 ++- 3 files changed, 123 insertions(+), 56 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs index a5047d1fe62..612324fdcc9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs @@ -1,5 +1,8 @@ +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using GraphQL; using GraphQL.Language.AST; using GraphQL.Types; using GraphQL.Validation; @@ -12,56 +15,50 @@ namespace OrchardCore.Apis.GraphQL.ValidationRules public class RequiresPermissionValidationRule : IValidationRule { public static readonly string ErrorCode = "Unauthorized"; + private readonly IAuthorizationService _authorizationService; + private readonly IStringLocalizer S; - public Task ValidateAsync(ValidationContext validationContext) + public RequiresPermissionValidationRule(IAuthorizationService authorizationService, IStringLocalizer s) { - var context = (GraphQLUserContext)validationContext.UserContext; - - // Todo: EnterLeaveListener has been removed and the signatures of INodeVisitor.Enter and INodeVisitor.Leave have changed. NodeVisitors class has been added in its place. - // https://graphql-dotnet.github.io/docs/migrations/migration4/ - // Ex: https://github.com/graphql-dotnet/graphql-dotnet/issues/2406 - INodeVisitor result = new NodeVisitors(); - return Task.FromResult(result); - - //return new EnterLeaveListener(_ => - //{ - // _.Match(op => - // { - // if (op.OperationType == OperationType.Mutation) - // { - // var authorizationManager = context.ServiceProvider.GetService(); - - // if (!authorizationManager.AuthorizeAsync(context.User, Permissions.ExecuteGraphQLMutations).GetAwaiter().GetResult()) - // { - // var localizer = context.ServiceProvider.GetService>(); - - // validationContext.ReportError(new ValidationError( - // validationContext.OriginalQuery, - // ErrorCode, - // localizer["Authorization is required to access {0}.", op.Name], - // op)); - // } - // } - // }); - - // _.Match(fieldAst => - // { - // var fieldDef = validationContext.TypeInfo.GetFieldDef(); - - // if (fieldDef.HasPermissions() && !Authorize(fieldDef, context)) - // { - // var localizer = context.ServiceProvider.GetService>(); - - // validationContext.ReportError(new ValidationError( - // validationContext.OriginalQuery, - // ErrorCode, - // localizer["Authorization is required to access the field. {0}", fieldAst.Name], - // fieldAst)); - // } - // }); - //}); + _authorizationService = authorizationService; + S = s; } + public async Task ValidateAsync(ValidationContext validationContext) + { + var operationType = OperationType.Query; + var userContext = (GraphQLUserContext)validationContext.UserContext; + + return await Task.FromResult(new NodeVisitors( + new MatchingNodeVisitor(async (astType, validationContext) => + { + operationType = astType.OperationType; + await AuthorizeOperationAsync(astType, validationContext, userContext, operationType, astType.Name); + }), + new MatchingNodeVisitor(async (objectFieldAst, validationContext) => + { + if (validationContext.TypeInfo.GetArgument()?.ResolvedType.GetNamedType() is IComplexGraphType argumentType) + { + var fieldType = argumentType.GetField(objectFieldAst.Name); + await AuthorizeNodePermissionAsync(objectFieldAst, fieldType, validationContext, userContext); + } + }), + new MatchingNodeVisitor(async (fieldAst, validationContext) => + { + var fieldDef = validationContext.TypeInfo.GetFieldDef(); + + if (fieldDef == null) + return; + + // check target field + await AuthorizeNodePermissionAsync(fieldAst, fieldDef, validationContext, userContext); + // check returned graph type + // AuthorizeNodePermissionAsync(fieldAst, fieldDef.ResolvedType.GetNamedType(), validationContext, userContext).GetAwaiter().GetResult(); // TODO: need to think of something to avoid this + }) + )); + } + + //private static bool Authorize(IProvideMetadata type, GraphQLUserContext context) //{ // //var authorizationManager = context.ServiceProvider.GetService(); @@ -72,5 +69,61 @@ public Task ValidateAsync(ValidationContext validationContext) //} + + private async Task AuthorizeOperationAsync(INode node, ValidationContext validationContext, GraphQLUserContext userContext, OperationType? operationType, string operationName) + { + if (operationType == OperationType.Mutation && !(await _authorizationService.AuthorizeAsync(userContext.User, Permissions.ExecuteGraphQLMutations))) + { + validationContext.ReportError(new ValidationError( + validationContext.Document.OriginalQuery, + ErrorCode, + S["Authorization is required to access {0}.", operationName], + node)); + } + } + + private async Task AuthorizeNodePermissionAsync(INode node, IFieldType fieldType, ValidationContext validationContext, GraphQLUserContext userContext) + { + if (!fieldType.HasPermissions()) { + return; + } + + var permissions = fieldType?.GetPermissions() ?? Enumerable.Empty(); + + if (permissions.Count() == 1) + { + var permission = permissions.First(); + // small optimization for the single policy - no 'new List<>()', no 'await Task.WhenAll()' + var authorizationResult = await _authorizationService.AuthorizeAsync(userContext.User, permission.Permission, permission.Resource); + if (!authorizationResult) + AddPermissionValidationError(validationContext, node, fieldType.Name); + } + else + { + var tasks = new List>(permissions.Count()); + + foreach (var permission in permissions) + { + var task = _authorizationService.AuthorizeAsync(userContext.User, permission.Permission, permission.Resource); + tasks.Add(task); + } + + var authorizationResults = await Task.WhenAll(tasks); + + if (authorizationResults.Any(x => !true)) + { + AddPermissionValidationError(validationContext, node, fieldType.Name); + } + } + } + + private void AddPermissionValidationError(ValidationContext validationContext, INode node, string nodeName) + { + validationContext.ReportError(new ValidationError( + validationContext.Document.OriginalQuery, + ErrorCode, + S["Authorization is required to access the node. {0}", nodeName], + node)); + } } } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/PermissionsExtensions.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/PermissionsExtensions.cs index 782877ad86c..a04e3070831 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/PermissionsExtensions.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/PermissionsExtensions.cs @@ -12,14 +12,17 @@ public static class PermissionsExtensions public static void RequirePermission(this IProvideMetadata type, Permission permission, object resource = null) { - var permissions = type.GetMetadata>(MetaDataKey); - - if (permissions == null) + lock (type) { - type.Metadata[MetaDataKey] = permissions = new List(); - } + var permissions = type.GetMetadata>(MetaDataKey); - permissions.Add(new GraphQLPermissionContext(permission, resource)); + if (permissions == null) + { + type.Metadata[MetaDataKey] = permissions = new List(); + } + + permissions.Add(new GraphQLPermissionContext(permission, resource)); + } } public static FieldBuilder RequirePermission(this FieldBuilder builder, Permission permission, object resource = null) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs index c16e6d554dd..428a7df2452 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs @@ -9,11 +9,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; +using Newtonsoft.Json; using OrchardCore.Apis.GraphQL; using OrchardCore.Apis.GraphQL.ValidationRules; using OrchardCore.Security.Permissions; using OrchardCore.Tests.Apis.Context; using Xunit; +using GraphQL.NewtonsoftJson; namespace OrchardCore.Tests.Apis.GraphQL.ValidationRules { @@ -38,7 +40,10 @@ public async Task FieldsWithNoRequirePermissionsShouldResolve() var executionResult = await executer.ExecuteAsync(options); Assert.Null(executionResult.Errors); - var result = JObject.FromObject(executionResult); + + var writer = new DocumentWriter(); + var result = JObject.Parse(await writer.WriteToStringAsync(executionResult)); + Assert.Equal("Fantastic Fox Hates Permissions", result["data"]["test"]["noPermissions"].ToString()); } @@ -59,7 +64,10 @@ public async Task FieldsWithRequirePermissionsShouldResolveWhenUserHasPermission var executionResult = await executer.ExecuteAsync(options); Assert.Null(executionResult.Errors); - var result = JObject.FromObject(executionResult); + + var writer = new DocumentWriter(); + var result = JObject.Parse(await writer.WriteToStringAsync(executionResult)); + Assert.Equal(expectedFieldValue, result["data"]["test"][fieldName].ToString()); } @@ -94,17 +102,20 @@ public async Task FieldsWithMultipleRequirePermissionsShouldResolveWhenUserHasAl var executionResult = await executer.ExecuteAsync(options); Assert.Null(executionResult.Errors); - var result = JObject.FromObject(executionResult); + + var writer = new DocumentWriter(); + var result = JObject.Parse(await writer.WriteToStringAsync(executionResult)); + Assert.Equal("Fantastic Fox Loves Multiple Permissions", result["data"]["test"]["permissionMultiple"].ToString()); } private ExecutionOptions BuildExecutionOptions(string query, PermissionsContext permissionsContext) { var services = new ServiceCollection(); - services.AddAuthorization(); services.AddLogging(); services.AddOptions(); + services.AddLocalization(); services.AddScoped(x => { From cad6999e8f973b961afb9b50cc5e95b1264a770f Mon Sep 17 00:00:00 2001 From: Carl Woodhouse Date: Tue, 30 Nov 2021 17:17:40 +0000 Subject: [PATCH 22/29] fix dynamic fields --- .../OrchardFieldNameConverter.cs | 15 +++++++++++++++ .../GraphQL/Fields/ContentFieldsProvider.cs | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs index 90a71e2ad9a..d707fd7f03d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs @@ -1,4 +1,5 @@ using System; +using GraphQL; using GraphQL.Conversion; using GraphQL.Types; @@ -15,6 +16,20 @@ public string NameForArgument(string fieldName, IComplexGraphType parentGraphTyp public string NameForField(string fieldName, IComplexGraphType parentGraphType) { + var attributes = parentGraphType?.GetType().GetCustomAttributes(typeof(GraphQLFieldNameAttribute), true); + + if (attributes != null) + { + foreach (GraphQLFieldNameAttribute attribute in attributes) + { + + if (attribute.Field == fieldName) + { + return attribute.Mapped; + } + } + } + return _defaultConverter.NameForField(fieldName, parentGraphType); } } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs index 4730b1759f7..d3781a7719a 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs @@ -61,7 +61,7 @@ public class ContentFieldsProvider : IContentFieldProvider Description = "Text field", FieldType = typeof(StringGraphType), UnderlyingType = typeof(TextField), - FieldAccessor = field => field.Content.Text + FieldAccessor = field => (string)field.Content.Text } }, { @@ -81,7 +81,7 @@ public class ContentFieldsProvider : IContentFieldProvider Description = "Multi text field", FieldType = typeof(ListGraphType), UnderlyingType = typeof(MultiTextField), - FieldAccessor = field => field.Content.Values + FieldAccessor = field => (string[]) field.Content.Values } } }; From cc3eb9146ce03455d7fcc04040b0caac1c15eb0c Mon Sep 17 00:00:00 2001 From: MikeKry <52829889+MikeKry@users.noreply.github.com> Date: Mon, 10 Jan 2022 12:35:48 +0100 Subject: [PATCH 23/29] Update graphql.net to 4.6.1 (#10782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update graphql.net from 2.4.0 to 4.6.1 * Update gql to 4.6.1 * Add service provider to execution options * Update field resolvers for textfield and multitextfield, add test fixes * revert uintentional change of .NET framework * mkdocs-material 8.0.2 * Updates based on other PR fix tests, remove services from graphqlcontext, fix where and orderby * fix MaxNumberOfResultsValidationRule * Fixes according to requested changes. Fix where condition with nested objects, fix where conditions with AddScalarFilterFields * Remove WriteToStringAsync method use, save some memory * Update src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs Co-authored-by: Sébastien Ros * using on memory stream Co-authored-by: Michal Kužela Co-authored-by: Antoine Griffard Co-authored-by: Sébastien Ros --- .../GraphQLMiddleware.cs | 25 ++++-- .../OrchardFieldNameConverter.cs | 7 +- .../Services/SchemaService.cs | 6 +- .../OrchardCore.Apis.GraphQL/Startup.cs | 10 +-- .../MaxNumberOfResultsValidationRule.cs | 90 +++++++++---------- .../RequiresPermissionValidationRule.cs | 22 ++--- .../GraphQL/Fields/ContentFieldsProvider.cs | 3 +- .../Fields/ObjectGraphTypeFieldProvider.cs | 2 +- .../GraphQL/MediaAssetObjectType.cs | 1 - .../GraphQL/MediaFieldQueryObjectType.cs | 1 - .../FieldBuilderResolverExtensions.cs | 1 - .../ResolveFieldContextExtensions.cs | 4 +- .../GraphQLUserContext.cs | 1 - .../Queries/WhereInputObjectGraphType.cs | 7 ++ .../Extensions/FieldTypeExtensions.cs | 1 - .../Queries/ContentItemsFieldType.cs | 14 +-- src/docs/requirements.txt | 2 +- .../Apis/GraphQL/Blog/BlogPostTests.cs | 3 +- .../GraphQL/ContentItemsFieldTypeTests.cs | 1 - .../RequiresPermissionValidationRuleTests.cs | 4 +- 20 files changed, 103 insertions(+), 102 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs index 951683cc2ba..aaf43cf0af4 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Mime; using System.Text; +using System.Threading; using System.Threading.Tasks; using GraphQL; using GraphQL.Execution; @@ -15,7 +16,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OrchardCore.Apis.GraphQL.Queries; using OrchardCore.Apis.GraphQL.ValidationRules; @@ -28,7 +28,6 @@ public class GraphQLMiddleware private readonly RequestDelegate _next; private readonly GraphQLSettings _settings; private readonly IDocumentExecuter _executer; - internal static readonly Encoding _utf8Encoding = new UTF8Encoding(false); private readonly static MediaType _jsonMediaType = new MediaType("application/json"); private readonly static MediaType _graphQlMediaType = new MediaType("application/graphql"); @@ -78,8 +77,6 @@ private bool IsGraphQLRequest(HttpContext context) private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaService, IDocumentWriter documentWriter) { - - GraphQLRequest request = null; // c.f. https://graphql.org/learn/serving-over-http/#post-request @@ -151,7 +148,6 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic _.OperationName = request.OperationName; _.Inputs = request.Variables.ToInputs(); _.UserContext = _settings.BuildUserContext?.Invoke(context); - _.RequestServices = context.RequestServices; _.ValidationRules = DocumentValidator.CoreRules .Concat(context.RequestServices.GetServices()); _.ComplexityConfiguration = new ComplexityConfiguration @@ -161,16 +157,31 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic FieldImpact = _settings.FieldImpact }; _.Listeners.Add(dataLoaderDocumentListener); + _.RequestServices = context.RequestServices; }); context.Response.StatusCode = (int)(result.Errors == null || result.Errors.Count == 0 ? HttpStatusCode.OK - : result.Errors.Any(x => x.Code == RequiresPermissionValidationRule.ErrorCode) + : result.Errors.Any(x => x is ValidationError ve && ve.Number == RequiresPermissionValidationRule.ErrorCode) ? HttpStatusCode.Unauthorized : HttpStatusCode.BadRequest); context.Response.ContentType = MediaTypeNames.Application.Json; - await documentWriter.WriteAsync(context.Response.Body, result); + + await WriteAsync(context.Response.Body, result, documentWriter); + } + + private async Task WriteAsync(Stream stream2, T value, IDocumentWriter documentWriter, CancellationToken cancellationToken = default) + { + // needs to be always async, otherwise __schema request is not working, direct write into response does not work as serialize is using sync method inside + cancellationToken.ThrowIfCancellationRequested(); + + using (MemoryStream stream = new MemoryStream()) + { + await documentWriter.WriteAsync(stream, value); + stream.Seek(0, SeekOrigin.Begin); + await stream.CopyToAsync(stream2, cancellationToken); + } } private static GraphQLRequest CreateRequestFromQueryString(HttpContext context, bool validateQueryKey = false) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs index d707fd7f03d..46a92e63640 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardFieldNameConverter.cs @@ -9,11 +9,13 @@ public class OrchardFieldNameConverter : INameConverter { private readonly INameConverter _defaultConverter = new CamelCaseNameConverter(); - public string NameForArgument(string fieldName, IComplexGraphType parentGraphType, FieldType field) + // todo: custom argument name? + public string NameForArgument(string argumentName, IComplexGraphType parentGraphType, FieldType field) { - return _defaultConverter.NameForArgument(fieldName, parentGraphType, field); + return _defaultConverter.NameForArgument(argumentName, parentGraphType, field); } + // TODO: check functionality public string NameForField(string fieldName, IComplexGraphType parentGraphType) { var attributes = parentGraphType?.GetType().GetCustomAttributes(typeof(GraphQLFieldNameAttribute), true); @@ -22,7 +24,6 @@ public string NameForField(string fieldName, IComplexGraphType parentGraphType) { foreach (GraphQLFieldNameAttribute attribute in attributes) { - if (attribute.Field == fieldName) { return attribute.Mapped; diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs index eb7d9605927..42659b4d315 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Services/SchemaService.cs @@ -15,14 +15,16 @@ namespace OrchardCore.Apis.GraphQL.Services public class SchemaService : ISchemaFactory { private readonly IEnumerable _schemaBuilders; + private readonly IServiceProvider _serviceProvider; private readonly SemaphoreSlim _schemaGenerationSemaphore = new SemaphoreSlim(1, 1); private readonly ConcurrentDictionary _identifiers = new ConcurrentDictionary(); private ISchema _schema; - public SchemaService(IEnumerable schemaBuilders) + public SchemaService(IEnumerable schemaBuilders, IServiceProvider serviceProvider) { _schemaBuilders = schemaBuilders; + _serviceProvider = serviceProvider; } public async Task GetSchemaAsync() @@ -63,7 +65,7 @@ public async Task GetSchemaAsync() var serviceProvider = ShellScope.Services; - var schema = new Schema(new SelfActivatingServiceProvider(serviceProvider)) + var schema = new Schema(new SelfActivatingServiceProvider(_serviceProvider)) { Query = new ObjectGraphType { Name = "Query" }, Mutation = new ObjectGraphType { Name = "Mutation" }, diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs index e82df391eb6..6308a61d4cf 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs @@ -6,7 +6,6 @@ using GraphQL.Validation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -40,16 +39,17 @@ public override void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + + services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddSingleton(services => { var settings = services.GetRequiredService>(); return new ErrorInfoProvider(new ErrorInfoProviderOptions { ExposeExceptionStackTrace = settings.Value.ExposeExceptions }); }); - services.AddSingleton(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddTransient(); diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs index ecfc9153d2b..9b625c1fc91 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/MaxNumberOfResultsValidationRule.cs @@ -12,67 +12,61 @@ public class MaxNumberOfResultsValidationRule : IValidationRule { private readonly int _maxNumberOfResults; private readonly MaxNumberOfResultsValidationMode _maxNumberOfResultsValidationMode; + private readonly IStringLocalizer _localizer; + private readonly ILogger _logger; - public MaxNumberOfResultsValidationRule(IOptions options) + public MaxNumberOfResultsValidationRule(IOptions options, IStringLocalizer localizer, ILogger logger) { var settings = options.Value; _maxNumberOfResults = settings.MaxNumberOfResults; _maxNumberOfResultsValidationMode = settings.MaxNumberOfResultsValidationMode; + _localizer = localizer; + _logger = logger; } public Task ValidateAsync(ValidationContext validationContext) { - // Todo: EnterLeaveListener has been removed and the signatures of INodeVisitor.Enter and INodeVisitor.Leave have changed. NodeVisitors class has been added in its place. - // https://graphql-dotnet.github.io/docs/migrations/migration4/ - // Ex: https://github.com/graphql-dotnet/graphql-dotnet/issues/2406 - INodeVisitor result = new NodeVisitors(); - return Task.FromResult(result); + return Task.FromResult((INodeVisitor)new NodeVisitors( + new MatchingNodeVisitor((arg, visitorContext) => + { + if ((arg.Name == "first" || arg.Name == "last") && arg.Value != null) + { + var context = (GraphQLUserContext)validationContext.UserContext; - //return new EnterLeaveListener(_ => - //{ - // _.Match(arg => - // { - // if ((arg.Name == "first" || arg.Name == "last") && arg.Value != null) - // { - // var context = (GraphQLUserContext)validationContext.UserContext; + int? value = null; - // int? value = null; + if (arg.Value is IntValue) + { + value = ((IntValue)arg.Value)?.Value; + } + else + { + if (validationContext.Inputs.TryGetValue(arg.Value.ToString(), out var input)) + { + value = (int?)input; + } + } - // if (arg.Value is IntValue) - // { - // value = ((IntValue)arg.Value)?.Value; - // } - // else - // { - // if (validationContext.Inputs.TryGetValue(arg.Value.ToString(), out var input)) - // { - // value = (int?)input; - // } - // } + if (value.HasValue && value > _maxNumberOfResults) + { + var errorMessage = _localizer["'{0}' exceeds the maximum number of results for '{1}' ({2})", value.Value, arg.Name, _maxNumberOfResults]; - // if (value.HasValue && value > _maxNumberOfResults) - // { - // var localizer = context.ServiceProvider.GetService>(); - // var errorMessage = localizer["'{0}' exceeds the maximum number of results for '{1}' ({2})", value.Value, arg.Name, _maxNumberOfResults]; - - // if (_maxNumberOfResultsValidationMode == MaxNumberOfResultsValidationMode.Enabled) - // { - // validationContext.ReportError(new ValidationError( - // validationContext.OriginalQuery, - // "ArgumentInputError", - // errorMessage, - // arg)); - // } - // else - // { - // var logger = context.ServiceProvider.GetService>(); - // logger.LogInformation(errorMessage); - // arg.Value = new IntValue(_maxNumberOfResults); // if disabled mode we just log info and override the arg to be maxvalue - // } - // } - // } - // }); - //}); + if (_maxNumberOfResultsValidationMode == MaxNumberOfResultsValidationMode.Enabled) + { + validationContext.ReportError(new ValidationError( + validationContext.Document.OriginalQuery, + "ArgumentInputError", + errorMessage, + arg)); + } + else + { + _logger.LogInformation(errorMessage); + arg = new Argument(arg.NameNode, new IntValue(_maxNumberOfResults)); // if disabled mode we just log info and override the arg to be maxvalue + } + } + } + }))); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs index 612324fdcc9..8becb4e6db8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/ValidationRules/RequiresPermissionValidationRule.cs @@ -26,14 +26,13 @@ public RequiresPermissionValidationRule(IAuthorizationService authorizationServi public async Task ValidateAsync(ValidationContext validationContext) { - var operationType = OperationType.Query; + // shouldn't we access UserContext from validationcontext inside MatchingNodeVisitor actions? var userContext = (GraphQLUserContext)validationContext.UserContext; return await Task.FromResult(new NodeVisitors( new MatchingNodeVisitor(async (astType, validationContext) => { - operationType = astType.OperationType; - await AuthorizeOperationAsync(astType, validationContext, userContext, operationType, astType.Name); + await AuthorizeOperationAsync(astType, validationContext, userContext, astType.OperationType, astType.Name); }), new MatchingNodeVisitor(async (objectFieldAst, validationContext) => { @@ -58,18 +57,6 @@ public async Task ValidateAsync(ValidationContext validationContex )); } - - //private static bool Authorize(IProvideMetadata type, GraphQLUserContext context) - //{ - // //var authorizationManager = context.ServiceProvider.GetService(); - - // //// awaitable IValidationRule in graphql dotnet is coming soon: - // //// https://github.com/graphql-dotnet/graphql-dotnet/issues/1140 - // //return type.GetPermissions().All(x => authorizationManager.AuthorizeAsync(context.User, x.Permission, x.Resource).GetAwaiter().GetResult()); - - - //} - private async Task AuthorizeOperationAsync(INode node, ValidationContext validationContext, GraphQLUserContext userContext, OperationType? operationType, string operationName) { if (operationType == OperationType.Mutation && !(await _authorizationService.AuthorizeAsync(userContext.User, Permissions.ExecuteGraphQLMutations))) @@ -84,7 +71,8 @@ private async Task AuthorizeOperationAsync(INode node, ValidationContext validat private async Task AuthorizeNodePermissionAsync(INode node, IFieldType fieldType, ValidationContext validationContext, GraphQLUserContext userContext) { - if (!fieldType.HasPermissions()) { + if (!fieldType.HasPermissions()) + { return; } @@ -98,7 +86,7 @@ private async Task AuthorizeNodePermissionAsync(INode node, IFieldType fieldType if (!authorizationResult) AddPermissionValidationError(validationContext, node, fieldType.Name); } - else + else { var tasks = new List>(permissions.Count()); diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs index d3781a7719a..7ef6e12deba 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using GraphQL.Resolvers; using GraphQL.Types; +using Newtonsoft.Json.Linq; using OrchardCore.ContentFields.Fields; using OrchardCore.ContentFields.GraphQL.Types; using OrchardCore.ContentManagement; @@ -81,7 +82,7 @@ public class ContentFieldsProvider : IContentFieldProvider Description = "Multi text field", FieldType = typeof(ListGraphType), UnderlyingType = typeof(MultiTextField), - FieldAccessor = field => (string[]) field.Content.Values + FieldAccessor = field => ((JArray)field.Content.Values)?.ToObject() } } }; diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs index fbd207433e7..c276e1cc826 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs @@ -35,7 +35,7 @@ public FieldType GetField(ContentPartFieldDefinition field) Type = queryGraphType, Resolver = new FuncFieldResolver(context => { - var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); // ReturnType.GetType().BaseType.GetGenericArguments().First(); + var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); // Check if part has been collapsed by trying to get the parent part. var contentPart = context.Source.Get(typeof(ContentPart), field.PartDefinition.Name); diff --git a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs index 2feb08e9d2f..3c9b20979a9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaAssetObjectType.cs @@ -19,7 +19,6 @@ public MediaAssetObjectType() .Resolve(x => { var path = x.Source.Path; - var context = (GraphQLUserContext)x.UserContext; var mediaFileStore = x.RequestServices.GetService(); return mediaFileStore.MapPathToPublicUrl(path); }); diff --git a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs index fcb72c1a6d2..1f8ba01b9aa 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/GraphQL/MediaFieldQueryObjectType.cs @@ -26,7 +26,6 @@ public MediaFieldQueryObjectType() .Resolve(x => { var paths = x.Page(x.Source.Paths); - var context = (GraphQLUserContext)x.UserContext; var mediaFileStore = x.RequestServices.GetService(); return paths.Select(p => mediaFileStore.MapPathToPublicUrl(p)); }); diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/FieldBuilderResolverExtensions.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/FieldBuilderResolverExtensions.cs index 0a905fff9e8..d48a0463e03 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/FieldBuilderResolverExtensions.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/FieldBuilderResolverExtensions.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using GraphQL; using GraphQL.Builders; -using GraphQL.Types; using OrchardCore.Apis.GraphQL.Resolvers; namespace OrchardCore.Apis.GraphQL diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs index c33124a5abc..76574c2aebb 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Extensions/ResolveFieldContextExtensions.cs @@ -15,7 +15,7 @@ public static bool HasPopulatedArgument(this IResolveFieldContext source, string { if (source.Arguments?.ContainsKey(argumentName) ?? false) { - return !string.IsNullOrEmpty((source.Arguments[argumentName].Value ?? string.Empty).ToString()); + return !string.IsNullOrEmpty(source.Arguments[argumentName].Value?.ToString()); }; return false; @@ -25,7 +25,7 @@ public static bool HasPopulatedArgument(this IResolveFieldContext { public ClaimsPrincipal User { get; set; } - public SemaphoreSlim ExecutionContextLock { get; } = new SemaphoreSlim(1, 1); } } diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs index c52896261ae..73b9fedf545 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs @@ -10,6 +10,13 @@ public class WhereInputObjectGraphType : WhereInputObjectGraphType public class WhereInputObjectGraphType : InputObjectGraphType { + // arguments of typed input graph types return typed object, without additional input fields (_in, _contains,..) + // so we return dictionary as it was before. + public override object ParseDictionary(IDictionary value) + { + return value; + } + // Applies to all types public static Dictionary EqualityOperators = new Dictionary { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/FieldTypeExtensions.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/FieldTypeExtensions.cs index bf729d2978e..b383a79a4cd 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/FieldTypeExtensions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Extensions/FieldTypeExtensions.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using GraphQL.Types; namespace OrchardCore.ContentManagement.GraphQL diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index e711bece543..8eae0a1b024 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -77,12 +77,8 @@ private async Task> Resolve(IResolveFieldContext contex JObject where = null; if (context.HasArgument("where")) { - var whereArgument = context.Arguments["where"]; - - if (whereArgument.Value != null) - { - where = JObject.FromObject(whereArgument.Value); - } + // context.Arguments[].Value is never null in GraphQL.NET 4 + where = JObject.FromObject(context.Arguments["where"].Value); } var session = context.RequestServices.GetService(); @@ -270,6 +266,12 @@ private void BuildExpressionsInternal(JObject where, Junction expressions, strin { foreach (var entry in where.Properties()) { + // new typed arguments return default null values + if (entry.Value.Type == JTokenType.Undefined || entry.Value.Type == JTokenType.Null) + { + continue; + } + IPredicate expression = null; var values = entry.Name.Split('_', 2); diff --git a/src/docs/requirements.txt b/src/docs/requirements.txt index 64774a82c41..80363b33026 100644 --- a/src/docs/requirements.txt +++ b/src/docs/requirements.txt @@ -1,5 +1,5 @@ mkdocs>=1.2.3 -mkdocs-material>=8.0.1 +mkdocs-material>=8.0.2 mkdocs-git-authors-plugin>=0.6.1 mkdocs-git-revision-date-localized-plugin>=0.10.3 pymdown-extensions>=9.1 diff --git a/test/OrchardCore.Tests/Apis/GraphQL/Blog/BlogPostTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/Blog/BlogPostTests.cs index 1d08eab6264..bd2e27226c9 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/Blog/BlogPostTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/Blog/BlogPostTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Threading.Tasks; using OrchardCore.Autoroute.Models; @@ -268,7 +269,7 @@ public async Task ShouldNotReturnBlogsWithoutViewBlogContentPermission() builder.WithField("contentItemId"); }); - Assert.Equal(GraphQLApi.ValidationRules.RequiresPermissionValidationRule.ErrorCode, result["errors"][0]["extensions"]["code"]); + Assert.Equal(GraphQLApi.ValidationRules.RequiresPermissionValidationRule.ErrorCode, result["errors"][0]["extensions"]["number"].ToString()); } } } diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index 2a8fea393f5..84a530672f3 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -243,7 +243,6 @@ public async Task ShouldBeAbleToUseTheSameIndexForMultipleAliases() services.Services.AddSingleton>(); services.Build(); - var context = CreateAnimalFieldContext(services); var ci = new ContentItem { ContentType = "Animal", Published = true, ContentItemId = "1", ContentItemVersionId = "1" }; diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs index 428a7df2452..5847f627333 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using GraphQL; using GraphQL.Conversion; +using GraphQL.Execution; using GraphQL.Types; using GraphQL.Validation; using Microsoft.AspNetCore.Authorization; @@ -123,7 +124,7 @@ private ExecutionOptions BuildExecutionOptions(string query, PermissionsContext }); services.AddScoped(); - + services.AddLocalization(); var serviceProvider = services.BuildServiceProvider(); return new ExecutionOptions @@ -132,7 +133,6 @@ private ExecutionOptions BuildExecutionOptions(string query, PermissionsContext Schema = new ValidationSchema(), UserContext = new GraphQLUserContext { - //ServiceProvider = serviceProvider, User = new ClaimsPrincipal(new StubIdentity()) }, ValidationRules = DocumentValidator.CoreRules.Concat(serviceProvider.GetServices()) From 098106bc036079aa31558237143389990eb0f581 Mon Sep 17 00:00:00 2001 From: MikeKry <52829889+MikeKry@users.noreply.github.com> Date: Thu, 28 Apr 2022 20:08:52 +0200 Subject: [PATCH 24/29] GraphQL4 - resolve async write using System.Text.Json (#11499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Kužela --- src/OrchardCore.Build/Dependencies.props | 2 +- .../GraphQLMiddleware.cs | 28 ++++++------------- .../GraphQLRequest.cs | 6 ++-- .../OrchardCore.Apis.GraphQL.csproj | 3 +- .../OrchardCore.Apis.GraphQL/Startup.cs | 2 +- 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index 19629d481cc..21faf2aae75 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -20,7 +20,7 @@ - + diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs index aaf43cf0af4..55ce92c3c63 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs @@ -4,11 +4,12 @@ using System.Net; using System.Net.Mime; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using GraphQL; using GraphQL.Execution; -using GraphQL.NewtonsoftJson; +using GraphQL.SystemTextJson; using GraphQL.Validation; using GraphQL.Validation.Complexity; using Microsoft.AspNetCore.Authentication; @@ -31,6 +32,7 @@ public class GraphQLMiddleware internal static readonly Encoding _utf8Encoding = new UTF8Encoding(false); private readonly static MediaType _jsonMediaType = new MediaType("application/json"); private readonly static MediaType _graphQlMediaType = new MediaType("application/graphql"); + private readonly static JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; public GraphQLMiddleware( RequestDelegate next, @@ -89,10 +91,11 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic if (mediaType.IsSubsetOf(_jsonMediaType) || mediaType.IsSubsetOf(_graphQlMediaType)) { - using var sr = new StreamReader(context.Request.Body); if (mediaType.IsSubsetOf(_graphQlMediaType)) { + using var sr = new StreamReader(context.Request.Body); + request = new GraphQLRequest { Query = await sr.ReadToEndAsync() @@ -100,8 +103,7 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic } else { - var json = await sr.ReadToEndAsync(); - request = JObject.Parse(json).ToObject(); + request = await JsonSerializer.DeserializeAsync(context.Request.Body, _jsonSerializerOptions); } } else @@ -140,7 +142,6 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic var schema = await schemaService.GetSchemaAsync(); var dataLoaderDocumentListener = context.RequestServices.GetRequiredService(); - var result = await _executer.ExecuteAsync(_ => { _.Schema = schema; @@ -168,20 +169,7 @@ private async Task ExecuteAsync(HttpContext context, ISchemaFactory schemaServic context.Response.ContentType = MediaTypeNames.Application.Json; - await WriteAsync(context.Response.Body, result, documentWriter); - } - - private async Task WriteAsync(Stream stream2, T value, IDocumentWriter documentWriter, CancellationToken cancellationToken = default) - { - // needs to be always async, otherwise __schema request is not working, direct write into response does not work as serialize is using sync method inside - cancellationToken.ThrowIfCancellationRequested(); - - using (MemoryStream stream = new MemoryStream()) - { - await documentWriter.WriteAsync(stream, value); - stream.Seek(0, SeekOrigin.Begin); - await stream.CopyToAsync(stream2, cancellationToken); - } + await documentWriter.WriteAsync(context.Response.Body, result); } private static GraphQLRequest CreateRequestFromQueryString(HttpContext context, bool validateQueryKey = false) @@ -203,7 +191,7 @@ private static GraphQLRequest CreateRequestFromQueryString(HttpContext context, if (context.Request.Query.ContainsKey("variables")) { - request.Variables = JObject.Parse(context.Request.Query["variables"]); + request.Variables = JsonSerializer.Deserialize(context.Request.Query["variables"], _jsonSerializerOptions); } if (context.Request.Query.ContainsKey("operationName")) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLRequest.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLRequest.cs index 269145bbbe2..5fababae1bb 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLRequest.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLRequest.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Text.Json; +using GraphQL; namespace OrchardCore.Apis.GraphQL { @@ -7,6 +8,7 @@ public class GraphQLRequest public string OperationName { get; set; } public string NamedQuery { get; set; } public string Query { get; set; } - public JObject Variables { get; set; } + + public JsonElement Variables { get; set; } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj index 92a5d7faa25..bed6320a45d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/OrchardCore.Apis.GraphQL.csproj @@ -15,9 +15,10 @@ + - + diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs index 6308a61d4cf..bfd5da0eabd 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs @@ -2,7 +2,7 @@ using GraphQL; using GraphQL.DataLoader; using GraphQL.Execution; -using GraphQL.NewtonsoftJson; +using GraphQL.SystemTextJson; using GraphQL.Validation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; From 2ddcc51afcdcbf52750e0a305455aeee63fc78ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 1 Sep 2022 11:30:02 -0700 Subject: [PATCH 25/29] Update RequiresPermissionValidationRuleTests.cs --- .../ValidationRules/RequiresPermissionValidationRuleTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs index 5847f627333..0efffe72184 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs @@ -16,7 +16,6 @@ using OrchardCore.Security.Permissions; using OrchardCore.Tests.Apis.Context; using Xunit; -using GraphQL.NewtonsoftJson; namespace OrchardCore.Tests.Apis.GraphQL.ValidationRules { From 78cf8d6a787208bff7652002a033335bd8aa1508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 1 Sep 2022 11:52:44 -0700 Subject: [PATCH 26/29] Update RequiresPermissionValidationRuleTests.cs --- .../ValidationRules/RequiresPermissionValidationRuleTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs index 0efffe72184..57fb195503f 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs @@ -7,6 +7,7 @@ using GraphQL.Execution; using GraphQL.Types; using GraphQL.Validation; +using GraphQL.SystemTextJson; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; From 89556f6d44005885c5f9ca9271243dae4112d13c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 1 Sep 2022 11:55:51 -0700 Subject: [PATCH 27/29] Reference NewtonSoft.Json --- .../ValidationRules/RequiresPermissionValidationRuleTests.cs | 1 - test/OrchardCore.Tests/OrchardCore.Tests.csproj | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs index 57fb195503f..0efffe72184 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs @@ -7,7 +7,6 @@ using GraphQL.Execution; using GraphQL.Types; using GraphQL.Validation; -using GraphQL.SystemTextJson; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; diff --git a/test/OrchardCore.Tests/OrchardCore.Tests.csproj b/test/OrchardCore.Tests/OrchardCore.Tests.csproj index 0ed8c1e2e32..81e8e3e04f1 100644 --- a/test/OrchardCore.Tests/OrchardCore.Tests.csproj +++ b/test/OrchardCore.Tests/OrchardCore.Tests.csproj @@ -80,6 +80,7 @@ + From b8eb3ba3cfb9816708fd2e8279afdd7b0a426de6 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 6 Sep 2022 11:22:56 -0700 Subject: [PATCH 28/29] Fix build --- .../ValidationRules/RequiresPermissionValidationRuleTests.cs | 3 +-- test/OrchardCore.Tests/OrchardCore.Tests.csproj | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs index 0efffe72184..a986f9392d3 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs @@ -4,13 +4,12 @@ using System.Threading.Tasks; using GraphQL; using GraphQL.Conversion; -using GraphQL.Execution; +using GraphQL.SystemTextJson; using GraphQL.Types; using GraphQL.Validation; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; -using Newtonsoft.Json; using OrchardCore.Apis.GraphQL; using OrchardCore.Apis.GraphQL.ValidationRules; using OrchardCore.Security.Permissions; diff --git a/test/OrchardCore.Tests/OrchardCore.Tests.csproj b/test/OrchardCore.Tests/OrchardCore.Tests.csproj index 81e8e3e04f1..0ed8c1e2e32 100644 --- a/test/OrchardCore.Tests/OrchardCore.Tests.csproj +++ b/test/OrchardCore.Tests/OrchardCore.Tests.csproj @@ -80,7 +80,6 @@ - From 8d9bc0afa26057954777c9ac2a314fb14db655f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 8 Sep 2022 10:13:04 -0700 Subject: [PATCH 29/29] Bump version to 4.8.0 --- src/OrchardCore.Build/Dependencies.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index 270eef0a957..743f1b00702 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -20,10 +20,10 @@ - - - - + + + +