From a8a8ec54e01ea513fb410420adf2601d52a6840f Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Tue, 28 May 2024 17:04:53 +0200 Subject: [PATCH 1/2] Fixes building the GraphQL schema. --- .../GraphQL/Fields/ContentFieldsProvider.cs | 12 +++---- .../Fields/ObjectGraphTypeFieldProvider.cs | 30 +++++----------- .../GraphQL/Startup.cs | 4 +-- .../GraphQL/FlowMetadataContentTypeBuilder.cs | 2 +- .../GraphQL/MenuItemContentTypeBuilder.cs | 2 +- .../Queries/ContentTypeQuery.cs | 2 +- .../Types/DynamicContentTypeBuilder.cs | 10 +++--- .../Queries/Types/DynamicPartGraphType.cs | 35 +++++++++++++------ .../Queries/Types/IContentFieldProvider.cs | 4 +-- .../Queries/Types/IContentTypeBuilder.cs | 2 +- .../Queries/Types/TypedContentTypeBuilder.cs | 28 ++++----------- 11 files changed, 58 insertions(+), 73 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs index 53a00dfb719..3b0a11a9b44 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Frozen; using System.Collections.Generic; using GraphQL.Resolvers; using GraphQL.Types; @@ -12,7 +13,7 @@ namespace OrchardCore.ContentFields.GraphQL.Fields { public class ContentFieldsProvider : IContentFieldProvider { - private static readonly Dictionary _contentFieldTypeMappings = new() + private static readonly FrozenDictionary _contentFieldTypeMappings = new Dictionary() { { nameof(BooleanField), @@ -84,16 +85,15 @@ public class ContentFieldsProvider : IContentFieldProvider FieldAccessor = field => ((MultiTextField)field).Values, } } - }; + }.ToFrozenDictionary(); - public FieldType GetField(ContentPartFieldDefinition field, string namedPartTechnicalName, string customFieldName) + public FieldType GetField(ISchema schema, ContentPartFieldDefinition field, string namedPartTechnicalName, string customFieldName) { - if (!_contentFieldTypeMappings.TryGetValue(field.FieldDefinition.Name, out var value)) + if (!_contentFieldTypeMappings.TryGetValue(field.FieldDefinition.Name, out var fieldDescriptor)) { return null; } - var fieldDescriptor = value; return new FieldType { Name = customFieldName ?? field.Name, @@ -116,7 +116,7 @@ public FieldType GetField(ContentPartFieldDefinition field, string namedPartTech }; } - public bool HasField(ContentPartFieldDefinition field) => _contentFieldTypeMappings.ContainsKey(field.FieldDefinition.Name); + public bool HasField(ISchema schema, ContentPartFieldDefinition field) => _contentFieldTypeMappings.ContainsKey(field.FieldDefinition.Name); private sealed class FieldTypeDescriptor { diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs index c88e173dce8..9fc020cd372 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs @@ -1,10 +1,7 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.GraphQL.Queries.Types; using OrchardCore.ContentManagement.Metadata.Models; @@ -13,17 +10,13 @@ namespace OrchardCore.ContentFields.GraphQL.Fields { public class ObjectGraphTypeFieldProvider : IContentFieldProvider { - private readonly IHttpContextAccessor _httpContextAccessor; - private static readonly ConcurrentDictionary _partObjectGraphTypes = new(); - - public ObjectGraphTypeFieldProvider(IHttpContextAccessor httpContextAccessor) + public ObjectGraphTypeFieldProvider() { - _httpContextAccessor = httpContextAccessor; } - public FieldType GetField(ContentPartFieldDefinition field, string namedPartTechnicalName, string customFieldName) + public FieldType GetField(ISchema schema, ContentPartFieldDefinition field, string namedPartTechnicalName, string customFieldName) { - var queryGraphType = GetObjectGraphType(field); + var queryGraphType = GetObjectGraphType(schema, field); if (queryGraphType != null) { @@ -31,7 +24,7 @@ public FieldType GetField(ContentPartFieldDefinition field, string namedPartTech { Name = customFieldName ?? field.Name, Description = field.FieldDefinition.Name, - Type = queryGraphType.GetType(), + ResolvedType = queryGraphType, Resolver = new FuncFieldResolver(context => { var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); @@ -51,16 +44,11 @@ public FieldType GetField(ContentPartFieldDefinition field, string namedPartTech return null; } - private IObjectGraphType GetObjectGraphType(ContentPartFieldDefinition field) - { - var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; - - return _partObjectGraphTypes.GetOrAdd(field.FieldDefinition.Name, - partName => serviceProvider.GetService>()? - .FirstOrDefault(x => x.GetType().BaseType.GetGenericArguments().First().Name == partName) - ); - } + public bool HasField(ISchema schema, ContentPartFieldDefinition field) => GetObjectGraphType(schema, field) != null; - public bool HasField(ContentPartFieldDefinition field) => GetObjectGraphType(field) != null; + private static IObjectGraphType GetObjectGraphType(ISchema schema, ContentPartFieldDefinition field) => + schema.AdditionalTypeInstances + .OfType() + .FirstOrDefault(x => x.GetType().BaseType.GetGenericArguments().First().Name == field.FieldDefinition.Name); } } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs index 6eff7d9ab16..704a4391eca 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs @@ -13,8 +13,8 @@ public class Startup : StartupBase { public override void ConfigureServices(IServiceCollection services) { - services.AddScoped(); - services.AddScoped(); + services.AddTransient(); + services.AddTransient(); services.AddObjectGraphType(); services.AddObjectGraphType(); diff --git a/src/OrchardCore.Modules/OrchardCore.Flows/GraphQL/FlowMetadataContentTypeBuilder.cs b/src/OrchardCore.Modules/OrchardCore.Flows/GraphQL/FlowMetadataContentTypeBuilder.cs index 79a62f2e601..697104e9050 100644 --- a/src/OrchardCore.Modules/OrchardCore.Flows/GraphQL/FlowMetadataContentTypeBuilder.cs +++ b/src/OrchardCore.Modules/OrchardCore.Flows/GraphQL/FlowMetadataContentTypeBuilder.cs @@ -8,7 +8,7 @@ namespace OrchardCore.Flows.GraphQL { public class FlowMetadataContentTypeBuilder : IContentTypeBuilder { - public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { if (contentTypeDefinition.GetStereotype() != "Widget") { diff --git a/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemContentTypeBuilder.cs b/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemContentTypeBuilder.cs index ead2711ce86..f8476e23840 100644 --- a/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemContentTypeBuilder.cs +++ b/src/OrchardCore.Modules/OrchardCore.Menu/GraphQL/MenuItemContentTypeBuilder.cs @@ -10,7 +10,7 @@ namespace OrchardCore.Menu.GraphQL { public class MenuItemContentTypeBuilder : IContentTypeBuilder { - public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { if (contentTypeDefinition.GetStereotype() != "MenuItem") { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentTypeQuery.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentTypeQuery.cs index 4da8b2dd550..71b508815c0 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentTypeQuery.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentTypeQuery.cs @@ -72,7 +72,7 @@ public async Task BuildAsync(ISchema schema) foreach (var builder in contentTypeBuilders) { - builder.Build(query, typeDefinition, typeType); + builder.Build(schema, query, typeDefinition, typeType); } // Limit queries to standard content types or those content types that are explicitly configured. diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index b699530f66c..3dd4ecb7450 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -28,7 +28,7 @@ public DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, S = localizer; } - public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; var contentFieldProviders = serviceProvider.GetServices().ToList(); @@ -53,7 +53,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin continue; } - if (!(part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(fieldProvider => fieldProvider.HasField(field))))) + if (!(part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(fieldProvider => fieldProvider.HasField(schema, field))))) { continue; } @@ -66,7 +66,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin { var customFieldName = GraphQLContentOptions.GetFieldName(part, part.Name, field.Name); - var fieldType = fieldProvider.GetField(field, part.Name, customFieldName); + var fieldType = fieldProvider.GetField(schema, field, part.Name, customFieldName); if (fieldType != null) { @@ -92,7 +92,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin { foreach (var fieldProvider in contentFieldProviders) { - var contentFieldType = fieldProvider.GetField(field, part.Name); + var contentFieldType = fieldProvider.GetField(schema, field, part.Name); if (contentFieldType != null && !contentItemType.HasField(contentFieldType.Name)) { @@ -121,7 +121,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin return context.Source.Get(typeToResolve, nameToResolve); }); - field.Type(new DynamicPartGraphType(_httpContextAccessor, part)); + field.Type(new DynamicPartGraphType(part)); _dynamicPartFields[partName] = field.FieldType; } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartGraphType.cs index 6d99b1cb789..cbf0eafc39c 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartGraphType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartGraphType.cs @@ -1,6 +1,6 @@ +using System; using System.Linq; using GraphQL.Types; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using OrchardCore.ContentManagement.Metadata.Models; @@ -8,25 +8,38 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types { public sealed class DynamicPartGraphType : ObjectGraphType { - public DynamicPartGraphType(IHttpContextAccessor httpContextAccessor, ContentTypePartDefinition part) + private ContentTypePartDefinition _part; + + public DynamicPartGraphType(ContentTypePartDefinition part) { Name = part.Name; + _part = part; + } - var serviceProvider = httpContextAccessor.HttpContext.RequestServices; - var contentFieldProviders = serviceProvider.GetServices().ToList(); - - foreach (var field in part.PartDefinition.Fields) + public override void Initialize(ISchema schema) + { + if (schema is IServiceProvider serviceProvider) { - foreach (var fieldProvider in contentFieldProviders) + var contentFieldProviders = serviceProvider.GetServices().ToList(); + + foreach (var field in _part.PartDefinition.Fields) { - var fieldType = fieldProvider.GetField(field, part.Name); - if (fieldType != null) + foreach (var fieldProvider in contentFieldProviders) { - AddField(fieldType); - break; + var fieldType = fieldProvider.GetField(schema, field, _part.Name); + if (fieldType != null) + { + AddField(fieldType); + break; + } } } } + + // Part is not required here anymore, do not keep it alive. + _part = null; + + base.Initialize(schema); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs index addbb2a00b7..ab498269fc8 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs @@ -5,8 +5,8 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types { public interface IContentFieldProvider { - FieldType GetField(ContentPartFieldDefinition field, string namedPartTechnicalName, string customFieldName = null); + FieldType GetField(ISchema schema, ContentPartFieldDefinition field, string namedPartTechnicalName, string customFieldName = null); - bool HasField(ContentPartFieldDefinition field); + bool HasField(ISchema schema, ContentPartFieldDefinition field); } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentTypeBuilder.cs index b61bb1f20f9..332dd71745f 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentTypeBuilder.cs @@ -5,7 +5,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types { public interface IContentTypeBuilder { - void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType); + void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType); void Clear() { } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs index e4a72031eee..b2a11b035ec 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Concurrent; using System.Linq; using GraphQL; using GraphQL.Resolvers; @@ -14,10 +12,6 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types { public class TypedContentTypeBuilder : IContentTypeBuilder { - private static readonly ConcurrentDictionary _partTypes = new(); - private static readonly ConcurrentDictionary _partObjectGraphTypes = new(); - private static readonly ConcurrentDictionary _partInputObjectGraphTypes = new(); - private readonly IHttpContextAccessor _httpContextAccessor; private readonly GraphQLContentOptions _contentOptions; @@ -28,7 +22,7 @@ public TypedContentTypeBuilder(IHttpContextAccessor httpContextAccessor, _contentOptions = contentOptionsAccessor.Value; } - public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; var typeActivator = serviceProvider.GetService>(); @@ -38,9 +32,6 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin return; } - var queryObjectGraphTypes = serviceProvider.GetServices(); - var queryInputGraphTypes = serviceProvider.GetServices(); - foreach (var part in contentTypeDefinition.Parts) { if (_contentOptions.ShouldSkip(part)) @@ -56,14 +47,8 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin continue; } - var partType = _partTypes.GetOrAdd(part.PartDefinition.Name, key => typeActivator.GetTypeActivator(key).Type); - var queryGraphType = _partObjectGraphTypes - .GetOrAdd(part.PartDefinition.Name, - partName => - { - return queryObjectGraphTypes.FirstOrDefault(x => x.GetType().BaseType.GetGenericArguments().First().Name == partName); - } - ); + var queryGraphType = schema.AdditionalTypeInstances.OfType() + .FirstOrDefault(x => x.GetType().BaseType.GetGenericArguments().First().Name == part.PartDefinition.Name); var collapsePart = _contentOptions.ShouldCollapse(part); @@ -78,6 +63,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin continue; } + var partType = typeActivator.GetTypeActivator(part.PartDefinition.Name).Type; var rolledUpField = new FieldType { Name = field.Name, @@ -124,10 +110,8 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin } } - var inputGraphTypeResolved = _partInputObjectGraphTypes.GetOrAdd(part.PartDefinition.Name, partName => - { - return queryInputGraphTypes.FirstOrDefault(x => x.GetType().BaseType.GetGenericArguments().FirstOrDefault()?.Name == part.PartDefinition.Name); - }); + var inputGraphTypeResolved = schema.AdditionalTypeInstances.OfType() + .FirstOrDefault(x => x.GetType().BaseType.GetGenericArguments().FirstOrDefault()?.Name == part.PartDefinition.Name); if (inputGraphTypeResolved != null) { From c0f7906121b3bb6c392ce75426af2b9c67ae10b1 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Thu, 30 May 2024 20:20:54 +0200 Subject: [PATCH 2/2] Don't use OfType<>(). --- .../GraphQL/Fields/ObjectGraphTypeFieldProvider.cs | 3 +-- .../Queries/Types/TypedContentTypeBuilder.cs | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs index 9fc020cd372..800d04cd706 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs @@ -48,7 +48,6 @@ public FieldType GetField(ISchema schema, ContentPartFieldDefinition field, stri private static IObjectGraphType GetObjectGraphType(ISchema schema, ContentPartFieldDefinition field) => schema.AdditionalTypeInstances - .OfType() - .FirstOrDefault(x => x.GetType().BaseType.GetGenericArguments().First().Name == field.FieldDefinition.Name); + .FirstOrDefault(x => x is IObjectGraphType && x.GetType().BaseType.GetGenericArguments().First().Name == field.FieldDefinition.Name) as IObjectGraphType; } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs index b2a11b035ec..58d239c2f40 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/TypedContentTypeBuilder.cs @@ -47,8 +47,8 @@ public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition continue; } - var queryGraphType = schema.AdditionalTypeInstances.OfType() - .FirstOrDefault(x => x.GetType().BaseType.GetGenericArguments().First().Name == part.PartDefinition.Name); + var queryGraphType = schema.AdditionalTypeInstances + .FirstOrDefault(x => x is IObjectGraphType && x.GetType().BaseType.GetGenericArguments().First().Name == part.PartDefinition.Name) as IObjectGraphType; var collapsePart = _contentOptions.ShouldCollapse(part); @@ -110,8 +110,8 @@ public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition } } - var inputGraphTypeResolved = schema.AdditionalTypeInstances.OfType() - .FirstOrDefault(x => x.GetType().BaseType.GetGenericArguments().FirstOrDefault()?.Name == part.PartDefinition.Name); + var inputGraphTypeResolved = schema.AdditionalTypeInstances + .FirstOrDefault(x => x is IInputObjectGraphType && x.GetType().BaseType.GetGenericArguments().FirstOrDefault()?.Name == part.PartDefinition.Name) as IInputObjectGraphType; if (inputGraphTypeResolved != null) {