Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ddieppa/dfff9a0ad1b2ce258a89b0ef36f1fd12 to your computer and use it in GitHub Desktop.
Save ddieppa/dfff9a0ad1b2ce258a89b0ef36f1fd12 to your computer and use it in GitHub Desktop.
Resolver scoping middleware (MediatR) for HotChocolate
[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 :)
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