From dcc49f087bcbb53acd72454e34c9660946639592 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Tue, 6 Feb 2024 09:55:51 -0500 Subject: [PATCH] Add IUserContextBuilder (#1118) --- README.md | 4 +++ .../GraphQLHttpMiddleware.cs | 20 +++++++++--- .../IUserContextBuilder.cs | 6 ++++ ....Server.Transports.AspNetCore.approved.txt | 3 ++ ....Server.Transports.AspNetCore.approved.txt | 3 ++ .../Middleware/MiscTests.cs | 31 +++++++++++++++++++ 6 files changed, 62 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e42698fc..0c90f78b 100644 --- a/README.md +++ b/README.md @@ -894,6 +894,10 @@ are long-lived, using scoped services within a user context builder will result scoped services having a matching long lifetime. You may wish to alleviate this by creating a service scope temporarily within your user context builder. +For applications that service multiple schemas, you may register `IUserContextBuilder` +to create a user context for a specific schema. This is useful when you need to create +a user context that is specific to a particular schema. + ### Mutations within GET requests For security reasons and pursuant to current recommendations, mutation GraphQL requests diff --git a/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs b/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs index 3aceec51..adf2a4cf 100644 --- a/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs +++ b/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs @@ -36,6 +36,15 @@ public GraphQLHttpMiddleware( : base(next, serializer, documentExecuter, serviceScopeFactory, options, hostApplicationLifetime) { } + + /// + protected override ValueTask?> BuildUserContextAsync(HttpContext context, object? payload) + { + var userContextBuilder = context.RequestServices.GetService>(); + return userContextBuilder == null + ? base.BuildUserContextAsync(context, payload) + : userContextBuilder.BuildUserContextAsync(context, payload); + } } /// @@ -677,17 +686,18 @@ protected virtual async Task ExecuteRequestAsync(HttpContext co /// via /// if needed. ///

- /// By default this method pulls the registered , - /// if any, within the service scope and executes it to build the user context. + /// By default this method pulls the registered + /// or instance, if any, within the service scope + /// and executes it to build the user context. /// In this manner, both scoped and singleton /// instances are supported, although singleton instances are recommended. ///
- protected virtual async ValueTask?> BuildUserContextAsync(HttpContext context, object? payload) + protected virtual ValueTask?> BuildUserContextAsync(HttpContext context, object? payload) { var userContextBuilder = context.RequestServices.GetService(); return userContextBuilder == null - ? null - : await userContextBuilder.BuildUserContextAsync(context, payload); + ? default // successful result of null + : userContextBuilder.BuildUserContextAsync(context, payload); } ValueTask?> IUserContextBuilder.BuildUserContextAsync(HttpContext context, object? payload) diff --git a/src/Transports.AspNetCore/IUserContextBuilder.cs b/src/Transports.AspNetCore/IUserContextBuilder.cs index c6764478..d88682d1 100644 --- a/src/Transports.AspNetCore/IUserContextBuilder.cs +++ b/src/Transports.AspNetCore/IUserContextBuilder.cs @@ -24,3 +24,9 @@ public interface IUserContextBuilder /// Dictionary object representing user context. Return to use default user context. ValueTask?> BuildUserContextAsync(HttpContext context, object? payload); } + +/// +public interface IUserContextBuilder : IUserContextBuilder + where TSchema : ISchema +{ +} diff --git a/tests/ApiApprovalTests/net50+net60+netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/net50+net60+netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt index 2686b29a..4fdc10f2 100644 --- a/tests/ApiApprovalTests/net50+net60+netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/net50+net60+netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -145,6 +145,7 @@ namespace GraphQL.Server.Transports.AspNetCore where TSchema : GraphQL.Types.ISchema { public GraphQLHttpMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.IGraphQLTextSerializer serializer, GraphQL.IDocumentExecuter documentExecuter, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddlewareOptions options, Microsoft.Extensions.Hosting.IHostApplicationLifetime hostApplicationLifetime) { } + protected override System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload) { } } public sealed class HttpGetValidationRule : GraphQL.Validation.IValidationRule { @@ -166,6 +167,8 @@ namespace GraphQL.Server.Transports.AspNetCore { System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload); } + public interface IUserContextBuilder : GraphQL.Server.Transports.AspNetCore.IUserContextBuilder + where TSchema : GraphQL.Types.ISchema { } public class UserContextBuilder : GraphQL.Server.Transports.AspNetCore.IUserContextBuilder where TUserContext : System.Collections.Generic.IDictionary { diff --git a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt index 6fd19749..d2d58630 100644 --- a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -152,6 +152,7 @@ namespace GraphQL.Server.Transports.AspNetCore where TSchema : GraphQL.Types.ISchema { public GraphQLHttpMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.IGraphQLTextSerializer serializer, GraphQL.IDocumentExecuter documentExecuter, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddlewareOptions options, GraphQL.Server.Transports.AspNetCore.IHostApplicationLifetime hostApplicationLifetime) { } + protected override System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload) { } } public class HostApplicationLifetime : GraphQL.Server.Transports.AspNetCore.IHostApplicationLifetime, Microsoft.Extensions.Hosting.IHostedService { @@ -184,6 +185,8 @@ namespace GraphQL.Server.Transports.AspNetCore { System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload); } + public interface IUserContextBuilder : GraphQL.Server.Transports.AspNetCore.IUserContextBuilder + where TSchema : GraphQL.Types.ISchema { } public class UserContextBuilder : GraphQL.Server.Transports.AspNetCore.IUserContextBuilder where TUserContext : System.Collections.Generic.IDictionary { diff --git a/tests/Transports.AspNetCore.Tests/Middleware/MiscTests.cs b/tests/Transports.AspNetCore.Tests/Middleware/MiscTests.cs index 4298bdbc..2ce6ddf7 100644 --- a/tests/Transports.AspNetCore.Tests/Middleware/MiscTests.cs +++ b/tests/Transports.AspNetCore.Tests/Middleware/MiscTests.cs @@ -32,6 +32,37 @@ public void Constructors() _ = new GraphQLHttpMiddleware(next, serializer, executer, scopeFactory, options, appLifetime); } + [Fact] + public async Task SchemaSpecificBuilders() + { + var middleware = new MyMiddleware( + Mock.Of(MockBehavior.Strict), + Mock.Of(MockBehavior.Strict), + Mock.Of>(MockBehavior.Strict), + Mock.Of(MockBehavior.Strict), + new GraphQLHttpMiddlewareOptions(), + Mock.Of(MockBehavior.Strict)); + var builder = new Mock>(MockBehavior.Strict); + var d = new Dictionary { { "test", "test" } }; + builder.Setup(x => x.BuildUserContextAsync(It.IsAny(), It.IsAny())).ReturnsAsync(d); + var serviceProviderMock = new Mock(MockBehavior.Strict); + serviceProviderMock.Setup(x => x.GetService(typeof(IUserContextBuilder))).Returns(builder.Object); + var contextMock = new Mock(MockBehavior.Strict); + contextMock.Setup(x => x.RequestServices).Returns(serviceProviderMock.Object); + (await middleware.MyBuildUserContextAsync(contextMock.Object, null)).ShouldBe(d); + } + + private class MyMiddleware : GraphQLHttpMiddleware + where TSchema : ISchema + { + public MyMiddleware(RequestDelegate next, IGraphQLTextSerializer serializer, IDocumentExecuter executer, IServiceScopeFactory scopeFactory, GraphQLHttpMiddlewareOptions options, IHostApplicationLifetime hostApplicationLifetime) + : base(next, serializer, executer, scopeFactory, options, hostApplicationLifetime) + { + } + + public ValueTask?> MyBuildUserContextAsync(HttpContext context, object? payload) => base.BuildUserContextAsync(context, payload); + } + [Fact] public async Task WriteErrorResponseString() {