-
-
Save ddieppa/dfff9a0ad1b2ce258a89b0ef36f1fd12 to your computer and use it in GitHub Desktop.
Resolver scoping middleware (MediatR) for HotChocolate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[UseResolverScopedMediator] | |
public async Task<string> GetAccountsAsync( | |
IResolverContext context, | |
[ScopedState] IMediator mediator) | |
=> await mediator.Send(new SomeRequest()); | |
// Note: Here mediator is scoped, and when the handler is instantiated, all its dependencies are generated in the same scope, | |
// including DbContext instances, so no multi-thread usage :) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Reflection; | |
using HotChocolate.Types; | |
using HotChocolate.Types.Descriptors; | |
using MediatR; | |
using Microsoft.Extensions.DependencyInjection; | |
namespace MyCompany.GraphQL | |
{ | |
/// <summary> | |
/// This attribute allows us to tap into the field resolution middleware | |
/// so that we can dynamically create an <see cref="IServiceScope"/> just for | |
/// this field resolver method, and create an <see cref="IMediator"/> instance inside it. | |
/// | |
/// This is used to solve a problem with using EF Core's DbContextPool, which scopes | |
/// DbContext instances per http request and will cause HotChocolate resolvers running | |
/// in parallel to execute on the same DbContext, which isn't allowed. | |
/// | |
/// By resolving our <see cref="IMediator"/> instance inside an isolated scope, | |
/// we ensure that if it depends itself on a DbContext that it will be unique in that scope | |
/// and there won't be multiple threads using it simultaneously. | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Method)] | |
public class UseResolverScopedMediatorAttribute : ObjectFieldDescriptorAttribute | |
{ | |
private static readonly string _injectedArgumentName = "mediator"; | |
private static readonly HashSet<string> _localSchemaNames = new HashSet<string> | |
{ | |
SchemaNames.Content, | |
SchemaNames.ShopTyre, | |
SchemaNames.Task | |
}; | |
public override void OnConfigure( | |
IDescriptorContext descriptorContext, | |
IObjectFieldDescriptor descriptor, | |
MemberInfo member) | |
{ | |
descriptor.Use(next => async context => | |
{ | |
// Temp workaround for: https://github.com/ChilliCream/hotchocolate/issues/2246 | |
using var scope = _localSchemaNames.Contains(context.Schema.Name) | |
? context.Service<Microsoft.AspNetCore.Http.IHttpContextAccessor>().HttpContext.RequestServices.CreateScope() | |
: context.Service<IServiceProvider>().CreateScope(); | |
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>(); | |
context.ModifyScopedContext(c => c.SetItem(_injectedArgumentName, mediator)); | |
await next(context); | |
}); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment