Skip to content

Commit

Permalink
Fixes building the GraphQL schema. (#16184)
Browse files Browse the repository at this point in the history
  • Loading branch information
gvkries authored May 31, 2024
1 parent 08ddb05 commit 8ef3a02
Show file tree
Hide file tree
Showing 11 changed files with 57 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using GraphQL.Resolvers;
using GraphQL.Types;
Expand All @@ -12,7 +13,7 @@ namespace OrchardCore.ContentFields.GraphQL.Fields
{
public class ContentFieldsProvider : IContentFieldProvider
{
private static readonly Dictionary<string, FieldTypeDescriptor> _contentFieldTypeMappings = new()
private static readonly FrozenDictionary<string, FieldTypeDescriptor> _contentFieldTypeMappings = new Dictionary<string, FieldTypeDescriptor>()
{
{
nameof(BooleanField),
Expand Down Expand Up @@ -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,
Expand All @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,25 +10,21 @@ namespace OrchardCore.ContentFields.GraphQL.Fields
{
public class ObjectGraphTypeFieldProvider : IContentFieldProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
private static readonly ConcurrentDictionary<string, IObjectGraphType> _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)
{
return new FieldType
{
Name = customFieldName ?? field.Name,
Description = field.FieldDefinition.Name,
Type = queryGraphType.GetType(),
ResolvedType = queryGraphType,
Resolver = new FuncFieldResolver<ContentElement, ContentElement>(context =>
{
var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First();
Expand All @@ -51,16 +44,10 @@ 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<IEnumerable<IObjectGraphType>>()?
.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
.FirstOrDefault(x => x is IObjectGraphType && x.GetType().BaseType.GetGenericArguments().First().Name == field.FieldDefinition.Name) as IObjectGraphType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IContentFieldProvider, ObjectGraphTypeFieldProvider>();
services.AddScoped<IContentFieldProvider, ContentFieldsProvider>();
services.AddTransient<IContentFieldProvider, ObjectGraphTypeFieldProvider>();
services.AddTransient<IContentFieldProvider, ContentFieldsProvider>();

services.AddObjectGraphType<LinkField, LinkFieldQueryObjectType>();
services.AddObjectGraphType<HtmlField, HtmlFieldQueryObjectType>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<IContentFieldProvider>().ToList();
Expand All @@ -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;
}
Expand All @@ -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)
{
Expand All @@ -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))
{
Expand Down Expand Up @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
using System;
using System.Linq;
using GraphQL.Types;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.ContentManagement.Metadata.Models;

namespace OrchardCore.ContentManagement.GraphQL.Queries.Types
{
public sealed class DynamicPartGraphType : ObjectGraphType<ContentPart>
{
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<IContentFieldProvider>().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<IContentFieldProvider>().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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() { }
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using GraphQL;
using GraphQL.Resolvers;
Expand All @@ -14,10 +12,6 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types
{
public class TypedContentTypeBuilder : IContentTypeBuilder
{
private static readonly ConcurrentDictionary<string, Type> _partTypes = new();
private static readonly ConcurrentDictionary<string, IObjectGraphType> _partObjectGraphTypes = new();
private static readonly ConcurrentDictionary<string, IInputObjectGraphType> _partInputObjectGraphTypes = new();

private readonly IHttpContextAccessor _httpContextAccessor;
private readonly GraphQLContentOptions _contentOptions;

Expand All @@ -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<ITypeActivatorFactory<ContentPart>>();
Expand All @@ -38,9 +32,6 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin
return;
}

var queryObjectGraphTypes = serviceProvider.GetServices<IObjectGraphType>();
var queryInputGraphTypes = serviceProvider.GetServices<IInputObjectGraphType>();

foreach (var part in contentTypeDefinition.Parts)
{
if (_contentOptions.ShouldSkip(part))
Expand All @@ -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
.FirstOrDefault(x => x is IObjectGraphType && x.GetType().BaseType.GetGenericArguments().First().Name == part.PartDefinition.Name) as IObjectGraphType;

var collapsePart = _contentOptions.ShouldCollapse(part);

Expand All @@ -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,
Expand Down Expand Up @@ -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
.FirstOrDefault(x => x is IInputObjectGraphType && x.GetType().BaseType.GetGenericArguments().FirstOrDefault()?.Name == part.PartDefinition.Name) as IInputObjectGraphType;

if (inputGraphTypeResolved != null)
{
Expand Down

0 comments on commit 8ef3a02

Please sign in to comment.