Skip to content

Commit

Permalink
Merge pull request #3 from EasyOC/main
Browse files Browse the repository at this point in the history
Main
  • Loading branch information
hyzx86 committed Mar 19, 2024
2 parents 60a2845 + 1f58aa3 commit 8b747a3
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Net;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using GraphQL;
using GraphQL.Execution;
Expand All @@ -28,6 +27,7 @@ namespace OrchardCore.Apis.GraphQL
public class GraphQLMiddleware : IMiddleware
{
private readonly GraphQLSettings _settings;
private readonly IGraphQLTextSerializer _graphQLTextSerializer;
private readonly IGraphQLSerializer _serializer;
private readonly IDocumentExecuter _executer;
internal static readonly Encoding _utf8Encoding = new UTF8Encoding(false);
Expand All @@ -37,11 +37,13 @@ public class GraphQLMiddleware : IMiddleware
public GraphQLMiddleware(
IOptions<GraphQLSettings> settingsOption,
IDocumentExecuter executer,
IGraphQLSerializer serializer)
IGraphQLSerializer serializer,
IGraphQLTextSerializer graphQLTextSerializer)
{
_settings = settingsOption.Value;
_executer = executer;
_serializer = serializer;
_graphQLTextSerializer = graphQLTextSerializer;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
Expand Down Expand Up @@ -89,19 +91,17 @@ private async Task ExecuteAsync(HttpContext context)

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 GraphQLNamedQueryRequest
{
Query = await sr.ReadToEndAsync()
};
}
else
{
request = await JsonSerializer.DeserializeAsync<GraphQLNamedQueryRequest>(context.Request.Body, JOptions.CamelCase);
request = _graphQLTextSerializer.Deserialize<GraphQLNamedQueryRequest>(await sr.ReadToEndAsync());
}
}
else
Expand Down Expand Up @@ -171,7 +171,7 @@ private async Task ExecuteAsync(HttpContext context)
await _serializer.WriteAsync(context.Response.Body, result);
}

private static GraphQLNamedQueryRequest CreateRequestFromQueryString(HttpContext context, bool validateQueryKey = false)
private GraphQLNamedQueryRequest CreateRequestFromQueryString(HttpContext context, bool validateQueryKey = false)
{
if (!context.Request.Query.ContainsKey("query"))
{
Expand All @@ -190,7 +190,7 @@ private static GraphQLNamedQueryRequest CreateRequestFromQueryString(HttpContext

if (context.Request.Query.ContainsKey("variables"))
{
request.Variables = JsonSerializer.Deserialize<Inputs>(context.Request.Query["variables"], JOptions.CamelCase);
request.Variables = _graphQLTextSerializer.Deserialize<Inputs>(context.Request.Query["variables"]);
}

if (context.Request.Query.ContainsKey("operationName"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using GraphQL;

namespace OrchardCore.Apis.GraphQL.Json;

public class GraphQLNamedQueryRequestJsonConverter : JsonConverter<GraphQLNamedQueryRequest>
{
public static readonly GraphQLNamedQueryRequestJsonConverter Instance = new();

/// <summary>
/// Name for the operation name parameter.
/// See https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#request-parameters
/// </summary>
private const string _operationNameKey = "operationName";

/// <summary>
/// Name for the query parameter.
/// See https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#request-parameters
/// </summary>
private const string _queryKey = "query";

/// <summary>
/// Name for the variables parameter.
/// See https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#request-parameters
/// </summary>
private const string _variablesKey = "variables";

/// <summary>
/// Name for the extensions parameter.
/// See https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#request-parameters
/// </summary>
private const string _extensionsKey = "extensions";

private const string _namedQueryKey = "namedQuery";


public override void Write(Utf8JsonWriter writer, GraphQLNamedQueryRequest value, JsonSerializerOptions options)
{
writer.WriteStartObject();
if (value.Query != null)
{
writer.WritePropertyName(_queryKey);
writer.WriteStringValue(value.Query);
}
if (value.OperationName != null)
{
writer.WritePropertyName(_operationNameKey);
writer.WriteStringValue(value.OperationName);
}
if (value.Variables != null)
{
writer.WritePropertyName(_variablesKey);
JsonSerializer.Serialize(writer, value.Variables, options);
}
if (value.Extensions != null)
{
writer.WritePropertyName(_extensionsKey);
JsonSerializer.Serialize(writer, value.Extensions, options);
}
if (value.NamedQuery != null)
{
writer.WritePropertyName(_namedQueryKey);
JsonSerializer.Serialize(writer, value.NamedQuery, options);
}
writer.WriteEndObject();
}

public override GraphQLNamedQueryRequest Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();

var request = new GraphQLNamedQueryRequest();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return request;
}

if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string key = reader.GetString()!;

//unexpected end of data
if (!reader.Read())
throw new JsonException();

switch (key)
{
case _queryKey:
request.Query = reader.GetString()!;
break;
case _operationNameKey:
request.OperationName = reader.GetString()!;
break;
case _namedQueryKey:
request.NamedQuery = reader.GetString();
break;
case _variablesKey:
request.Variables = JsonSerializer.Deserialize<Inputs>(ref reader, options);
break;
case _extensionsKey:
request.Extensions = JsonSerializer.Deserialize<Inputs>(ref reader, options);
break;
default:
reader.Skip();
break;
}
}

//unexpected end of data
throw new JsonException();
}
}
18 changes: 17 additions & 1 deletion src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using OrchardCore.Apis.GraphQL.Json;
using OrchardCore.Apis.GraphQL.Services;
using OrchardCore.Apis.GraphQL.ValidationRules;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Extensions;
using OrchardCore.Json;
using OrchardCore.Json.Extensions;
using OrchardCore.Modules;
using OrchardCore.Navigation;
using OrchardCore.Security.Permissions;
Expand Down Expand Up @@ -46,7 +50,19 @@ public override void ConfigureServices(IServiceCollection services)
services.AddScoped<IPermissionProvider, Permissions>();
services.AddTransient<INavigationProvider, AdminMenu>();
services.AddSingleton<GraphQLMiddleware>();
services.AddGraphQL(builder => builder.AddSystemTextJson());
services.Configure<ContentSerializerJsonOptionsConfiguration>(options =>
{
});

services.AddGraphQL(builder => builder.AddSystemTextJson((options, sp) =>
{
// Common types of converters are already configured in the assembly "GraphQL.SystemTextJson".
options.Converters.Add(GraphQLNamedQueryRequestJsonConverter.Instance);
var contentSerializerJsonOptions = sp.GetRequiredService<IOptions<ContentSerializerJsonOptions>>().Value;
options.Merge(contentSerializerJsonOptions.SerializerOptions);
}));

services.AddOptions<GraphQLSettings>().Configure<IShellConfiguration>((c, configuration) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public override void ConfigureServices(IServiceCollection services)
services.AddObjectGraphType<LinkField, LinkFieldQueryObjectType>();
services.AddObjectGraphType<HtmlField, HtmlFieldQueryObjectType>();
services.AddObjectGraphType<ContentPickerField, ContentPickerFieldQueryObjectType>();
services.AddObjectGraphType<UserPickerField, UserPickerFieldQueryObjectType>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System.Collections.Generic;
using System.Linq;
using GraphQL;
using GraphQL.DataLoader;
using GraphQL.Types;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Apis.GraphQL;
using OrchardCore.ContentFields.Fields;
using OrchardCore.ContentManagement;
using OrchardCore.Users.GraphQL;
using OrchardCore.Users.Indexes;
using OrchardCore.Users.Models;
using YesSql;
using YesSql.Services;

namespace OrchardCore.ContentFields.GraphQL
{
public class UserPickerFieldQueryObjectType : ObjectGraphType<UserPickerField>
{
public UserPickerFieldQueryObjectType(UserType userType)
{
Name = nameof(UserPickerField);

Field<ListGraphType<StringGraphType>, IEnumerable<string>>("userIds")
.Description("user ids")
.PagingArguments()
.Resolve(x =>
{
return x.Page(x.Source.UserIds);
});

Field<ListGraphType<UserType>, IEnumerable<User>>("users")
.Type(new ListGraphType(userType))
.Description("the user items")
.PagingArguments()
.ResolveAsync(x =>
{
var userLoader = GetOrAddUserProfileByIdDataLoader(x);
return userLoader.LoadAsync(x.Page(x.Source.UserIds)).Then(itemResultSet =>
{
return itemResultSet.SelectMany(x => x);
});
});
Field<UserType, User>("user")
.Type(userType)
.Description("the first user")
.ResolveAsync(x =>
{
var userLoader = GetOrAddUserProfileByIdDataLoader(x);
return userLoader.LoadAsync(x.Source.UserIds.FirstOrDefault()).Then(itemResultSet =>
{
return itemResultSet.FirstOrDefault();
});
});
}

public static IDataLoader<string, IEnumerable<User>> GetOrAddUserProfileByIdDataLoader<T>(IResolveFieldContext<T> context)
{
IDataLoaderContextAccessor requiredService = context.RequestServices.GetRequiredService<IDataLoaderContextAccessor>();
var session = context.RequestServices.GetService<ISession>();
return requiredService.Context.GetOrAddCollectionBatchLoader("GetOrAddUserByIds", async (IEnumerable<string> userIds) =>
{
if (userIds == null || !userIds.Any())
{
return null;
}
var users = await session.Query<User, UserIndex>(y => y.UserId.IsIn(userIds)).ListAsync();
return users.ToLookup((User k) => k.UserId, (User user) => user);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Shortcodes.Abstractions\OrchardCore.Shortcodes.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.ResourceManagement\OrchardCore.ResourceManagement.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Users.Core\OrchardCore.Users.Core.csproj" />
<ProjectReference Include="..\OrchardCore.Users\OrchardCore.Users.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public class Startup : StartupBase
public override void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISchemaBuilder, CurrentUserQuery>();
services.AddTransient<UserType>();
services.AddScoped<UserType>();
}
}
36 changes: 20 additions & 16 deletions src/OrchardCore.Modules/OrchardCore.Users/GraphQL/UserType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using GraphQL.Types;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.GraphQL.Queries.Types;
using OrchardCore.ContentManagement.Metadata.Models;
using OrchardCore.Users.Models;
using OrchardCore.Users.Services;
Expand Down Expand Up @@ -30,27 +32,29 @@ public UserType(IStringLocalizer<UserType> localizer)
internal void AddField(ISchema schema, ContentTypeDefinition typeDefinition)
{
var contentItemType = schema.AdditionalTypeInstances.SingleOrDefault(t => t.Name == typeDefinition.Name);

if (contentItemType == null)
{
// This error would indicate that this graph type is build too early.
throw new InvalidOperationException("ContentTypeDefinition has not been registered in GraphQL");
}

var field = Field(typeDefinition.Name, contentItemType.GetType())
.Description(S["Custom user settings of {0}.", typeDefinition.DisplayName])
.ResolveAsync(static async context => {
// We don't want to create an empty content item if it does not exist.
if (context.Source is User user &&
user.Properties.ContainsKey(context.FieldDefinition.ResolvedType.Name))
{
var customUserSettingsService = context.RequestServices!.GetRequiredService<CustomUserSettingsService>();
var settingsType = await customUserSettingsService.GetSettingsTypeAsync(context.FieldDefinition.ResolvedType.Name);
return await customUserSettingsService.GetSettingsAsync(user, settingsType);
}
return null;
});
Field<ContentItemInterface, ContentItem>(typeDefinition.Name)
.Type(contentItemType)
.Description(S["Custom user settings of {0}.", typeDefinition.DisplayName])
.ResolveAsync(static async context =>
{
// We don't want to create an empty content item if it does not exist.
if (context.Source is User user &&
user.Properties.ContainsKey(context.FieldDefinition.ResolvedType.Name))
{
var customUserSettingsService = context.RequestServices!.GetRequiredService<CustomUserSettingsService>();
var settingsType = await customUserSettingsService.GetSettingsTypeAsync(context.FieldDefinition.ResolvedType.Name);
return await customUserSettingsService.GetSettingsAsync(user, settingsType);
}
return null;
});
}
}

0 comments on commit 8b747a3

Please sign in to comment.