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);