-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix DynamicContentTypeBuilder field double registration #12424
Fix DynamicContentTypeBuilder field double registration #12424
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
After more debugging it turns out that current check isn't ok, because HasField is method that checks strings with StringComparison.Ordinal (GraphQL core method, allows for fields to have fields with same names but different casing) but Orchard uses OrchardFieldNameConverter in Schema definition which converts any field name in camelcase. So on refresh of Schema in runtime, there could occur same exception, of field with two definitions So I'd argue to use ToCamelCase on FieldType.Name in both IContentFieldProvider, similar as we convert every part's name with ToFieldName to define some consistency of camelCasing field names in graphql |
Okay so need someone that better know GraphQl than me ;) |
Any feedback on this? I know @carlwoodhouse made graphql modules and it's his child 😀 but I cannot notice he is short on time for orchard lately. Is there chance for someone else to take ownership over graphql because upgrading graphql also took a lot of time? |
/cc @hyzx86 |
@tropcicstefan |
I have something like this with multiple fields. And no custom Object has been built with TypedContentTypeBuilder and DynamicContentTypeBuilder was prevented with the 'default field' with the name of But issue is if OrchardFieldNameConverter uses default camel case then field providers should too |
Have you tried implementing ISchemaBuilder? Just like this : I rewrote the Lucene query schema to return statistics using EasyOC.GraphQL.Queries.Types;
using GraphQL;
using GraphQL.Types;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OrchardCore.Apis.GraphQL;
using OrchardCore.Apis.GraphQL.Resolvers;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.GraphQL.Queries;
using OrchardCore.Search.Lucene;
using OrchardCore.Queries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LuceneQueryResults = OrchardCore.Search.Lucene.LuceneQueryResults;
namespace EasyOC.GraphQL.Queries
{
public class EOCLuceneQueryFieldTypeProvider : ISchemaBuilder
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<EOCLuceneQueryFieldTypeProvider> _logger;
public EOCLuceneQueryFieldTypeProvider(IHttpContextAccessor httpContextAccessor,
ILogger<EOCLuceneQueryFieldTypeProvider> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public Task<string> GetIdentifierAsync()
{
var queryManager = _httpContextAccessor.HttpContext.RequestServices.GetService<IQueryManager>();
return queryManager.GetIdentifierAsync();
}
public async Task BuildAsync(ISchema schema)
{
var queryManager = _httpContextAccessor.HttpContext.RequestServices.GetService<IQueryManager>();
var queries = await queryManager.ListQueriesAsync();
foreach (var query in queries.OfType<LuceneQuery>())
{
if (string.IsNullOrWhiteSpace(query.Schema))
continue;
var name = query.Name;
try
{
var querySchema = JObject.Parse(query.Schema);
if (!querySchema.ContainsKey("type"))
{
_logger.LogError("The Query '{Name}' schema is invalid, the 'type' property was not found.",
name);
continue;
}
var type = querySchema["type"].ToString();
if (querySchema.ContainsKey("hasTotal") && querySchema["hasTotal"].ToString()
.Equals("true", StringComparison.OrdinalIgnoreCase))
{
var fieldTypeName = querySchema["fieldTypeName"]?.ToString() ?? query.Name;
FieldType fieldType = schema.Query.GetField(fieldTypeName);
if (fieldType == null)
{
continue;
}
if (query.ReturnContentItems &&
type.StartsWith("ContentItem/", StringComparison.OrdinalIgnoreCase))
{
var contentType = type.Remove(0, 12);
BuildTotalContentTypeFieldType(fieldType, schema, contentType, query, fieldTypeName);
}
else
{
BuildTotalSchemaBasedFieldType(fieldType, query, querySchema, fieldTypeName);
}
}
}
catch (Exception e)
{
_logger.LogError(e, "The Query '{Name}' has an invalid schema.", name);
}
}
}
private FieldType BuildTotalSchemaBasedFieldType(FieldType fieldType, LuceneQuery query, JToken querySchema,
string fieldTypeName)
{
var properties = querySchema["properties"];
if (properties == null)
{
return null;
}
var totalType = new ObjectGraphType<TotalQueryResults>() { Name = fieldTypeName };
var typetype = new ObjectGraphType<JObject> { Name = fieldTypeName };
var listType = new ListGraphType(typetype);
totalType.Field(listType.GetType(), "items",
resolve: context =>
{
return context.Source?.Items;
});
var total = totalType.Field<IntGraphType>("total",
resolve: context =>
{
return context.Source?.Total;
});
foreach (JProperty child in properties.Children())
{
var name = child.Name;
var nameLower = name.Replace('.', '_');
var type = child.Value["type"].ToString();
var description = child.Value["description"]?.ToString();
if (type == "string")
{
var field = typetype.Field(
typeof(StringGraphType),
nameLower,
description: description,
resolve: context =>
{
var source = context.Source;
return source[context.FieldDefinition.Metadata["Name"].ToString()].ToObject<string>();
});
field.Metadata.Add("Name", name);
}
else if (type == "integer")
{
var field = typetype.Field(
typeof(IntGraphType),
nameLower,
description: description,
resolve: context =>
{
var source = context.Source;
return source[context.FieldDefinition.Metadata["Name"].ToString()].ToObject<int>();
});
field.Metadata.Add("Name", name);
}
}
fieldType.Arguments = new QueryArguments(
new QueryArgument<StringGraphType> { Name = "parameters" }
);
fieldType.Name = fieldTypeName;
fieldType.Description = "Represents the " + query.Source + " Query : " + query.Name;
fieldType.ResolvedType = totalType;
fieldType.Resolver = new LockedAsyncFieldResolver<object, object>(async context =>
{
var queryManager = context.RequestServices.GetService<IQueryManager>();
var iquery = await queryManager.GetQueryAsync(query.Name);
var parameters = context.GetArgument<string>("parameters");
var queryParameters = parameters != null
? JsonConvert.DeserializeObject<Dictionary<string, object>>(parameters)
: new Dictionary<string, object>();
var result = await queryManager.ExecuteQueryAsync(iquery, queryParameters) as LuceneQueryResults;
return result;
});
fieldType.Type = totalType.GetType();
return fieldType;
}
private FieldType BuildTotalContentTypeFieldType(FieldType fieldType, ISchema schema, string contentType,
LuceneQuery query, string fieldTypeName)
{
var typetype = schema.Query.Fields.OfType<ContentItemsFieldType>()
.FirstOrDefault(x => x.Name == contentType);
if (typetype == null)
{
return null;
}
var totalType = new ObjectGraphType<TotalQueryResults> { Name = fieldTypeName };
var items = totalType.Field(typetype.Type, "items",
resolve: context =>
{
return context.Source?.Items ?? Array.Empty<ContentItem>();
});
items.ResolvedType = typetype.ResolvedType;
totalType.Field<IntGraphType>("total",
resolve: context =>
{
return context.Source?.Total ?? 0;
});
fieldType.Arguments = new QueryArguments(
new QueryArgument<StringGraphType> { Name = "parameters" });
fieldType.Name = fieldTypeName;
fieldType.Description = "Represents the " + query.Source + " Query : " + query.Name;
fieldType.ResolvedType = totalType;
fieldType.Resolver = new LockedAsyncFieldResolver<object, object>(async context =>
{
var queryManager = context.RequestServices.GetService<IQueryManager>();
var iquery = await queryManager.GetQueryAsync(query.Name);
var parameters = context.GetArgument<string>("parameters");
var queryParameters = parameters != null
? JsonConvert.DeserializeObject<Dictionary<string, object>>(parameters)
: new Dictionary<string, object>();
var result = await queryManager.ExecuteQueryAsync(iquery, queryParameters);
return new TotalQueryResults
{
Total = (result as LuceneQueryResults)?.Count, Items = result?.Items ?? Array.Empty<ContentItem>()
};
});
fieldType.Type = totalType.GetType();
return fieldType;
}
}
}
|
You can try to query your custom field this way and then update it. var typetype = schema.Query.Fields.OfType<ContentItemsFieldType>()
.FirstOrDefault(x => x.Name == contentType); |
If this PR is a no go I think it's cleaner to go with custom IContentTypeBuilder to have some out of the box features as default ContentItem filters, permissions and possibility to add typed index filters. |
I don't think there's anything wrong with this PR, after all, line 90 below does the same thing, right Lines 60 to 93 in 2db2808
|
Fixes #12423