Field Middleware
Field Middleware provides additional behaviors during field resolution. GraphQL.NET supports two types:
- Global Middleware - Applied to all fields across the entire schema
- Field-Specific Middleware - Applied to individual fields (v8.7.0+)
Both types work similarly to ASP.NET Core HTTP middleware, executing in a chain where each middleware can perform actions before and after the next middleware or resolver.
Creating Middleware
Middleware is created by implementing the IFieldMiddleware interface:
public class LoggingMiddleware : IFieldMiddleware { private readonly ILogger<LoggingMiddleware> _logger; public LoggingMiddleware(ILogger<LoggingMiddleware> logger) { _logger = logger; } public async ValueTask<object?> ResolveAsync(IResolveFieldContext context, FieldMiddlewareDelegate next) { _logger.LogInformation("Resolving {Field}", context.FieldName); var result = await next(context); _logger.LogInformation("Resolved {Field}", context.FieldName); return result; } }The same middleware class can be used as either global or field-specific middleware depending on how you register it.
Global Middleware
Global middleware applies to all fields in your schema. This is useful for cross-cutting concerns like logging, metrics, or authorization.
Using UseMiddleware
The recommended approach is using UseMiddleware<T>() on the GraphQL builder:
services.AddGraphQL(b => b .AddSchema<MySchema>() .UseMiddleware<InstrumentFieldsMiddleware>() .UseMiddleware<LoggingMiddleware>());This automatically registers the middleware in DI as a singleton and applies it to the schema.
Manual Registration
You can also register middleware directly on the schema:
public class MySchema : Schema { public MySchema(IServiceProvider services, MyQuery query, LoggingMiddleware middleware) : base(services) { Query = query; FieldMiddleware.Use(middleware); } }Or use a lambda:
schema.FieldMiddleware.Use(next => async context => { // Code before resolver var result = await next(context); // Code after resolver return result; });Field-Specific Middleware
Field-specific middleware applies only to designated fields, offering better performance and clearer intent than global middleware for field-level concerns. There are three ways to apply middleware to a field:
// Register middleware in DI first if applicable services.AddSingleton<LoggingMiddleware>(); public class MyGraphType : ObjectGraphType { public MyGraphType() { Field<StringGraphType>("field1") .Resolve(context => "Data") // 1. Using a lambda .ApplyMiddleware(next => async context => { // Custom logic here var result = await next(context); return result; }); Field<StringGraphType>("field2") .Resolve(context => "Data") // 2. Using a middleware instance .ApplyMiddleware(new LoggingMiddleware(logger)); Field<StringGraphType>("field3") .Resolve(context => "Data") // 3. Using a type resolved from DI (recommended) .ApplyMiddleware<LoggingMiddleware>(); } }Execution Order
When both global and field-specific middleware are present:
- Global middleware (in registration order)
- Field-specific middleware (in application order)
- Field resolver
Example:
// Global middleware services.AddGraphQL(b => b .AddSchema<MySchema>() .UseMiddleware<GlobalMiddleware1>() .UseMiddleware<GlobalMiddleware2>()); // Field-specific middleware public class MyGraphType : ObjectGraphType { public MyGraphType() { Field<StringGraphType>("myField") .Resolve(context => "Result") .ApplyMiddleware<FieldMiddleware1>() .ApplyMiddleware<FieldMiddleware2>(); } } // Execution order: // 1. GlobalMiddleware1 (before) // 2. GlobalMiddleware2 (before) // 3. FieldMiddleware1 (before) // 4. FieldMiddleware2 (before) // 5. Field Resolver executes // 6. FieldMiddleware2 (after) // 7. FieldMiddleware1 (after) // 8. GlobalMiddleware2 (after) // 9. GlobalMiddleware1 (after)Dependency Injection
Using DI with Global Middleware
When using UseMiddleware<T>(), the middleware is automatically registered as a singleton. For manual registration:
services.AddSingleton<LoggingMiddleware>(); public class MySchema : Schema { public MySchema(IServiceProvider services, MyQuery query, LoggingMiddleware middleware) : base(services) { Query = query; FieldMiddleware.Use(middleware); } }Using DI with Field-Specific Middleware
When using ApplyMiddleware<T>(), the middleware must be registered in the DI container:
// Register middleware in DI services.AddSingleton<AuthorizationMiddleware>(); // Apply to field - middleware is resolved from DI during schema initialization Field<StringGraphType>("protectedField") .Resolve(context => "Protected data") .ApplyMiddleware<AuthorizationMiddleware>();Note: The middleware is resolved from DI during schema initialization, not during each field resolution.
Scoped Dependencies
For scoped dependencies, use a singleton middleware and resolve dependencies in ResolveAsync:
public class MyMiddleware : IFieldMiddleware { public async ValueTask<object?> ResolveAsync(IResolveFieldContext context, FieldMiddlewareDelegate next) { var scopedService = context.RequestServices! .GetRequiredService<IMyScopedService>(); // Use scoped service return await next(context); } }Lifetime Considerations
Recommended lifetimes for optimal performance:
| Schema | Graph Type | Middleware | Recommendation |
|---|---|---|---|
| singleton | singleton | singleton | ✅ Recommended |
| scoped | scoped | singleton | ⚠️ Less performant |
| scoped | scoped | scoped | ⚠️ Least performant |
| scoped | singleton | scoped | ❌ Avoid - causes duplicate middleware application |
| singleton | singleton | scoped | ❌ Avoid - throws InvalidOperationException |
Important: Middleware is applied during schema initialization. Using incompatible lifetimes can cause middleware to be applied multiple times or fail to resolve.
Field Middleware vs Directives
Use Field Middleware when:
- You need programmatic control over field behavior
- The behavior is implementation-specific (not part of the schema contract)
- You want to apply logic to specific fields without schema changes
Use Directives when:
- The behavior should be visible in schema introspection
- You want schema-first configuration
- The behavior applies to multiple schema elements (types, fields, arguments)
For more information, see Directives.
Interface Reference
public interface IFieldMiddleware { ValueTask<object?> ResolveAsync(IResolveFieldContext context, FieldMiddlewareDelegate next); } public delegate ValueTask<object?> FieldMiddlewareDelegate(IResolveFieldContext context);