.NET Core API 带有 GraphQL 和动态字典
.NET Core API with GraphQL and dynamic Dictionaries
我有一个 .NET Core API 应用程序,它允许用户创建 具有特定架构的模型(很像普通的 Headless CMS)。
这些模型充当内容的模板。
在模型之上创建的内容存储在 NoSQL 数据库中,拥有该内容的 class 是这样的:
public class Content
{
public string Id { get; set; }
public IDictionary<string, object> Data { get; set; }
}
使用此结构,我可以通过 API 以 JSON 格式在系统中配置的所有类型的内容提供服务,因此我可以拥有例如 Product表示的内容是这样的:
{
"id": "625ea48672b93f0d68a9c886",
"data": {
"code": "ABC",
"has-serial-number": true,
"in-catalogue": true,
"in-customer-care": true,
"name": "Product Name",
"main-image": {
"id": "32691756a12ac10a5983f845",
"code": "fjgq7ur3OGo",
"type": "image",
"originalFileName": "myimage.png",
"format": "png",
"mimeType": "image/png",
"size": 4983921,
"url": "https://domain/imageurl.xyz",
"height": 1125,
"width": 2436,
"metadata": {
"it-IT": {
"title": "Image Title"
}
}
},
"main-gallery": [{
"id": "62691756a57cc10a5983f845",
"code": "fjgq7ur3OGo",
"type": "image",
"originalFileName": "myimage.png",
"format": "png",
"mimeType": "image/png",
"size": 4983921,
"url": "https://domain/imageurl.xyz",
"height": 1125,
"width": 2436,
"metadata": {
"it-IT": {
"title": "Image Title"
}
}
}
],
"thumbnail-video": null,
"subtitle": "Product subtitle",
"description": "Product description",
"brochure": true,
"category": [{
"id": "525964hfwhh0af373ef6",
"data": {
"name": "Category Name",
"appMenuIcon": {
"id": "62691756a57cc10a5983f845",
"code": "fjgq7ur3OGo",
"type": "image",
"originalFileName": "mycategoryimage.png",
"format": "png",
"mimeType": "image/png",
"size": 4983921,
"url": "https://domain/imageurl.xyz",
"height": 1125,
"width": 2436,
"metadata": {
"it-IT": {
"title": "Image title"
}
}
},
"subtitle": "Category subtitle",
"media": "6258058f632b390a189402df",
"content": "Category description"
}
},
],
}}
或者可以是这样的Post
{
"id": "6270f5f63934a209c0f0f9a2",
"data": {
"title": "Post title",
"date": "2022-04-28T00:00:00+00:00",
"type": "News",
"content": "Post content",
"author": "Author name",
"tags": ["tag1","tag2","tag3"],
"media": {
"id": "6270f5f03934a209c0f0f9a1",
"code": "ZvFuBP4Ism9",
"type": "image",
"originalFileName": "image.jpg",
"format": "jpg",
"mimeType": "image/jpeg",
"size": 329571,
"url": "https://domain/imageurl.xyz",
"height": 1609,
"width": 2560,
"metadata": {
"it-IT": {
"title": "image title"
}
}
},
"linkWebSite": "link to web"
}
}
等等。
每种类型的模型都存储在 NoSQL DB 上自己的集合中,当我查询它们时,我的存储库接受模型类型以了解从哪个集合获取内容。
到目前为止一切都很好,我可以通过 API 提供这些内容。
现在,我的客户要求我添加一个仅用于查询文档的 GraphQL 层(因此没有突变也没有订阅)。
我尝试检查 HotChocolate 和 graphql-dotnet,但我没有找到任何让它工作的方法。
看起来它们非常适合处理在 class 中定义了所有属性的所有场景,但这里我有:
- 许多类型,但只有 1 个 class 代表它们
- class 有一个字典来存储数据,因此没有预先创建 clr 属性
- 那些库似乎只在启动时工作,但我可以在运行时创建新模型
你有没有遇到过类似的情况要处理,万一怎么可能解决?
谢谢
可以动态创建模式。这实际上取决于您希望如何将数据呈现给 GraphQL API 的用户。 GraphQL 是静态类型的,因此通常您会创建带有字段的对象来表示数据。
Federation 对象中有一个 _Any 标量类型,如果您想走那条路,您可以使用它来直接支持字典,详情可在此处找到:
https://github.com/graphql-dotnet/graphql-dotnet/issues/669#issuecomment-674273598
尽管如果您想创建一个包含可查询的单个对象和字段的动态模式,这里是一个示例控制台应用程序,它是一种方法。
using GraphQL;
using GraphQL.Types;
using GraphQL.SystemTextJson;
var postContent = new Content
{
Id = Guid.NewGuid().ToString("D"),
Data = new Dictionary<string, object>
{
{ "title", "Post Title 1" },
{ "content", "Post content 2" },
{ "date", DateTime.Today }
}
};
// creating graph types based on the content of the object
var postType = new ObjectGraphType { Name = "Post" };
postType.Field("id", new IdGraphType());
foreach (var pair in postContent.Data)
{
// this attempts to find a maching GraphQL IGraphType based on the
// .NET type of pair.Value
// see all of the type mappings here:
// https://graphql-dotnet.github.io/docs/getting-started/schema-types
// you could also have your own mapping, which you will probably
// need to for nested types in the dictionary - ex:
// string -> StringGraphType
// DateTime -> DateGraphType
// etc.
var clrGraphType = pair.Value.GetType().GetGraphTypeFromType(isNullable: true);
// this is assuming the given types have no constructor arguments - use
// your preferred DI container to resolve complex types
IGraphType graphType = (IGraphType)Activator.CreateInstance(clrGraphType);
Console.WriteLine($"GraphType for {pair.Key} is {clrGraphType.Name}");
postType.Field(
pair.Key,
graphType,
resolve: fieldContext =>
{
// get the original Content object
var content = (Content)fieldContext.Source;
// lookup the value of the field based on the field name
// since that is what was used to create the type
return content.Data[fieldContext.FieldDefinition.Name];
});
}
var queryType = new ObjectGraphType();
queryType.Field(
"posts",
new ListGraphType(postType),
resolve: context =>
{
// lookup posts in the DB
var posts = new List<Content>
{
postContent,
new Content
{
Id = Guid.NewGuid().ToString("D"),
Data = new Dictionary<string, object>
{
{ "title", "Post Title 2" },
{ "content", "Post content 2" },
{ "date", DateTime.Today.AddDays(-1) }
}
}
};
return posts;
});
ISchema schema = new Schema { Query = queryType };
schema.RegisterType(postType);
var json = await schema.ExecuteAsync(_ =>
{
_.Schema = schema;
_.Query = "{ posts { id title content date } }";
// can use this to debug schema setup
_.ThrowOnUnhandledException = true;
});
Console.WriteLine(json);
class Content
{
public string Id { get; set; }
public IDictionary<string, object> Data { get; set; }
}
示例输出:
DynamicGraphQL% dotnet run
GraphType for title is StringGraphType
GraphType for content is StringGraphType
GraphType for date is DateTimeGraphType
{
"data": {
"posts": [
{
"id": "19d5bf3ab65c4632968597aa444eef40",
"title": "Post Title 1",
"content": "Post content 2",
"date": "2022-05-11T00:00:00-07:00"
},
{
"id": "29bb6ee9a86b42a1af1caa7ccbe4a6cd",
"title": "Post Title 2",
"content": "Post content 2",
"date": "2022-05-10T00:00:00-07:00"
}
]
}
}
这里还有一些OrchardCMS用来动态构建字段的示例代码。
public Task BuildAsync(ISchema schema)
{
var field = new FieldType
{
Name = "ContentItem",
Description = S["Content items are instances of content types, just like objects are instances of classes."],
Type = typeof(ContentItemInterface),
Arguments = new QueryArguments(
new QueryArgument<NonNullGraphType<StringGraphType>>
{
Name = "contentItemId",
Description = S["Content item id"]
}
),
Resolver = new AsyncFieldResolver<ContentItem>(ResolveAsync)
};
schema.Query.AddField(field);
return Task.CompletedTask;
}
希望对您有所帮助!
我有一个 .NET Core API 应用程序,它允许用户创建 具有特定架构的模型(很像普通的 Headless CMS)。 这些模型充当内容的模板。
在模型之上创建的内容存储在 NoSQL 数据库中,拥有该内容的 class 是这样的:
public class Content
{
public string Id { get; set; }
public IDictionary<string, object> Data { get; set; }
}
使用此结构,我可以通过 API 以 JSON 格式在系统中配置的所有类型的内容提供服务,因此我可以拥有例如 Product表示的内容是这样的:
{
"id": "625ea48672b93f0d68a9c886",
"data": {
"code": "ABC",
"has-serial-number": true,
"in-catalogue": true,
"in-customer-care": true,
"name": "Product Name",
"main-image": {
"id": "32691756a12ac10a5983f845",
"code": "fjgq7ur3OGo",
"type": "image",
"originalFileName": "myimage.png",
"format": "png",
"mimeType": "image/png",
"size": 4983921,
"url": "https://domain/imageurl.xyz",
"height": 1125,
"width": 2436,
"metadata": {
"it-IT": {
"title": "Image Title"
}
}
},
"main-gallery": [{
"id": "62691756a57cc10a5983f845",
"code": "fjgq7ur3OGo",
"type": "image",
"originalFileName": "myimage.png",
"format": "png",
"mimeType": "image/png",
"size": 4983921,
"url": "https://domain/imageurl.xyz",
"height": 1125,
"width": 2436,
"metadata": {
"it-IT": {
"title": "Image Title"
}
}
}
],
"thumbnail-video": null,
"subtitle": "Product subtitle",
"description": "Product description",
"brochure": true,
"category": [{
"id": "525964hfwhh0af373ef6",
"data": {
"name": "Category Name",
"appMenuIcon": {
"id": "62691756a57cc10a5983f845",
"code": "fjgq7ur3OGo",
"type": "image",
"originalFileName": "mycategoryimage.png",
"format": "png",
"mimeType": "image/png",
"size": 4983921,
"url": "https://domain/imageurl.xyz",
"height": 1125,
"width": 2436,
"metadata": {
"it-IT": {
"title": "Image title"
}
}
},
"subtitle": "Category subtitle",
"media": "6258058f632b390a189402df",
"content": "Category description"
}
},
],
}}
或者可以是这样的Post
{
"id": "6270f5f63934a209c0f0f9a2",
"data": {
"title": "Post title",
"date": "2022-04-28T00:00:00+00:00",
"type": "News",
"content": "Post content",
"author": "Author name",
"tags": ["tag1","tag2","tag3"],
"media": {
"id": "6270f5f03934a209c0f0f9a1",
"code": "ZvFuBP4Ism9",
"type": "image",
"originalFileName": "image.jpg",
"format": "jpg",
"mimeType": "image/jpeg",
"size": 329571,
"url": "https://domain/imageurl.xyz",
"height": 1609,
"width": 2560,
"metadata": {
"it-IT": {
"title": "image title"
}
}
},
"linkWebSite": "link to web"
}
}
等等。
每种类型的模型都存储在 NoSQL DB 上自己的集合中,当我查询它们时,我的存储库接受模型类型以了解从哪个集合获取内容。
到目前为止一切都很好,我可以通过 API 提供这些内容。
现在,我的客户要求我添加一个仅用于查询文档的 GraphQL 层(因此没有突变也没有订阅)。
我尝试检查 HotChocolate 和 graphql-dotnet,但我没有找到任何让它工作的方法。 看起来它们非常适合处理在 class 中定义了所有属性的所有场景,但这里我有:
- 许多类型,但只有 1 个 class 代表它们
- class 有一个字典来存储数据,因此没有预先创建 clr 属性
- 那些库似乎只在启动时工作,但我可以在运行时创建新模型
你有没有遇到过类似的情况要处理,万一怎么可能解决?
谢谢
可以动态创建模式。这实际上取决于您希望如何将数据呈现给 GraphQL API 的用户。 GraphQL 是静态类型的,因此通常您会创建带有字段的对象来表示数据。
Federation 对象中有一个 _Any 标量类型,如果您想走那条路,您可以使用它来直接支持字典,详情可在此处找到:
https://github.com/graphql-dotnet/graphql-dotnet/issues/669#issuecomment-674273598
尽管如果您想创建一个包含可查询的单个对象和字段的动态模式,这里是一个示例控制台应用程序,它是一种方法。
using GraphQL;
using GraphQL.Types;
using GraphQL.SystemTextJson;
var postContent = new Content
{
Id = Guid.NewGuid().ToString("D"),
Data = new Dictionary<string, object>
{
{ "title", "Post Title 1" },
{ "content", "Post content 2" },
{ "date", DateTime.Today }
}
};
// creating graph types based on the content of the object
var postType = new ObjectGraphType { Name = "Post" };
postType.Field("id", new IdGraphType());
foreach (var pair in postContent.Data)
{
// this attempts to find a maching GraphQL IGraphType based on the
// .NET type of pair.Value
// see all of the type mappings here:
// https://graphql-dotnet.github.io/docs/getting-started/schema-types
// you could also have your own mapping, which you will probably
// need to for nested types in the dictionary - ex:
// string -> StringGraphType
// DateTime -> DateGraphType
// etc.
var clrGraphType = pair.Value.GetType().GetGraphTypeFromType(isNullable: true);
// this is assuming the given types have no constructor arguments - use
// your preferred DI container to resolve complex types
IGraphType graphType = (IGraphType)Activator.CreateInstance(clrGraphType);
Console.WriteLine($"GraphType for {pair.Key} is {clrGraphType.Name}");
postType.Field(
pair.Key,
graphType,
resolve: fieldContext =>
{
// get the original Content object
var content = (Content)fieldContext.Source;
// lookup the value of the field based on the field name
// since that is what was used to create the type
return content.Data[fieldContext.FieldDefinition.Name];
});
}
var queryType = new ObjectGraphType();
queryType.Field(
"posts",
new ListGraphType(postType),
resolve: context =>
{
// lookup posts in the DB
var posts = new List<Content>
{
postContent,
new Content
{
Id = Guid.NewGuid().ToString("D"),
Data = new Dictionary<string, object>
{
{ "title", "Post Title 2" },
{ "content", "Post content 2" },
{ "date", DateTime.Today.AddDays(-1) }
}
}
};
return posts;
});
ISchema schema = new Schema { Query = queryType };
schema.RegisterType(postType);
var json = await schema.ExecuteAsync(_ =>
{
_.Schema = schema;
_.Query = "{ posts { id title content date } }";
// can use this to debug schema setup
_.ThrowOnUnhandledException = true;
});
Console.WriteLine(json);
class Content
{
public string Id { get; set; }
public IDictionary<string, object> Data { get; set; }
}
示例输出:
DynamicGraphQL% dotnet run
GraphType for title is StringGraphType
GraphType for content is StringGraphType
GraphType for date is DateTimeGraphType
{
"data": {
"posts": [
{
"id": "19d5bf3ab65c4632968597aa444eef40",
"title": "Post Title 1",
"content": "Post content 2",
"date": "2022-05-11T00:00:00-07:00"
},
{
"id": "29bb6ee9a86b42a1af1caa7ccbe4a6cd",
"title": "Post Title 2",
"content": "Post content 2",
"date": "2022-05-10T00:00:00-07:00"
}
]
}
}
这里还有一些OrchardCMS用来动态构建字段的示例代码。
public Task BuildAsync(ISchema schema)
{
var field = new FieldType
{
Name = "ContentItem",
Description = S["Content items are instances of content types, just like objects are instances of classes."],
Type = typeof(ContentItemInterface),
Arguments = new QueryArguments(
new QueryArgument<NonNullGraphType<StringGraphType>>
{
Name = "contentItemId",
Description = S["Content item id"]
}
),
Resolver = new AsyncFieldResolver<ContentItem>(ResolveAsync)
};
schema.Query.AddField(field);
return Task.CompletedTask;
}
希望对您有所帮助!