Dependency Injection

If you are unfamiliar with dependency injection, the following articles provide a good starting point:

Dependency injection with Hot Chocolate works almost the same as with a regular ASP.NET Core application. Nothing changes about how you add services to the DI container.

C#
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddSingleton<MySingletonService>()
.AddScoped<MyScopedService>()
.AddTransient<MyTransientService>();

Implicit Service Injection

In v16, Hot Chocolate automatically recognizes types registered as services in the DI container and injects them into resolver method parameters without requiring any attribute. This works similarly to Minimal APIs parameter binding.

When the execution engine encounters a resolver parameter whose type is registered in the DI container, it resolves the service automatically. You do not need to apply the [Service] attribute.

Resolver Injection

Inject dependencies into your resolvers as method arguments. This is the recommended approach.

Learn more about why constructor injection into GraphQL types is a bad idea

Injecting dependencies at the method level has several benefits:

  • The execution engine can optimize the resolver and adjust the execution strategy based on the needs of a specific service.
  • Refactoring (moving the resolver method between classes) becomes easier because the resolver does not depend on its outer class.

In the following example, BookService is injected automatically when it is registered as a service in the DI container:

C#
[QueryType]
public static class Query
{
public static async Task<Book?> GetBookByIdAsync(
Guid id,
BookService bookService)
{
return await bookService.GetBookAsync(id);
}
}

Default Scope

By default, scoped services are scoped to the resolver for queries and DataLoaders, and to the current request for mutations. This means that each execution of a query or DataLoader that accepts a scoped service receives a separate instance, avoiding threading issues with services that do not support multi-threading (for example, Entity Framework DbContexts). Since mutations are executed sequentially, they receive the same request-scoped instance.

You can change these defaults globally:

C#
builder
.AddGraphQL()
.ModifyOptions(o =>
{
o.DefaultQueryDependencyInjectionScope =
DependencyInjectionScope.Resolver;
o.DefaultMutationDependencyInjectionScope =
DependencyInjectionScope.Request;
});

You can also override the scope on a per-resolver basis:

C#
[QueryType]
public static class Query
{
[UseRequestScope]
public static async Task<Book?> GetBookByIdAsync(
Guid id,
BookService bookService) => // ...
}

Application Services in Schema Services

Hot Chocolate maintains a separate internal service provider for schema services. If you need application services to be available within schema-level components (such as diagnostic event listeners, error filters, or interceptors), you must cross-register them using AddApplicationService<T>():

C#
builder.Services.AddSingleton<MyService>();
builder
.AddGraphQL()
.AddApplicationService<MyService>()
.AddDiagnosticEventListener<MyDiagnosticEventListener>();

Services registered via AddApplicationService<T>() are resolved once during schema initialization from the application service provider and registered as singletons in the schema service provider.

The following configuration APIs require AddApplicationService<T>() for any application services they depend on:

  • AddHttpRequestInterceptor
  • AddSocketSessionInterceptor
  • AddErrorFilter
  • AddDiagnosticEventListener
  • AddOperationCompilerOptimizer
  • AddTransactionScopeHandler
  • AddRedisOperationDocumentStorage
  • AddAzureBlobStorageOperationDocumentStorage
  • AddInstrumentation with a custom ActivityEnricher

Note: Service injection into resolvers is not affected by this. Resolvers continue to use the application service provider directly.

Constructor Injection

When starting out with Hot Chocolate you might be inclined to inject dependencies into your GraphQL type definitions using the constructor.

You should avoid doing this, because:

  • GraphQL type definitions are singletons and your injected dependency will also become a singleton.
  • Access to this dependency cannot be synchronized by Hot Chocolate during request execution.

This does not apply within your own dependencies. Your ServiceA class can still inject ServiceB through the constructor.

When you need to access dependency injection services in your resolvers, use the method-level dependency injection approach described above.

Keyed Services

A keyed service registered like this:

C#
builder.Services.AddKeyedScoped<BookService>("bookService");

...can be accessed in your resolver with the [Service] attribute specifying the key:

C#
[QueryType]
public static class Query
{
public static async Task<Book?> GetBookByIdAsync(
Guid id,
[Service("bookService")] BookService bookService)
{
return await bookService.GetBookAsync(id);
}
}

Switching the Service Provider

While Hot Chocolate's internals rely on Microsoft's dependency injection container, you are not required to manage your own dependencies using this container. By default, Hot Chocolate uses the request-scoped HttpContext.RequestServices IServiceProvider to provide services to your resolvers.

You can switch out the service provider used for GraphQL requests, as long as your DI container implements the IServiceProvider interface.

To switch the service provider, call SetServices on the OperationRequestBuilder in both the IHttpRequestInterceptor and the ISocketSessionInterceptor.

C#
public sealed class HttpRequestInterceptor
: DefaultHttpRequestInterceptor
{
public override async ValueTask OnCreateAsync(
HttpContext context,
IRequestExecutor requestExecutor,
OperationRequestBuilder requestBuilder,
CancellationToken cancellationToken)
{
// keeping these lines is important!
await base.OnCreateAsync(
context,
requestExecutor,
requestBuilder,
cancellationToken);
requestBuilder.SetServices(YOUR_SERVICE_PROVIDER);
}
}
public sealed class SocketSessionInterceptor
: DefaultSocketSessionInterceptor
{
public override async ValueTask OnRequestAsync(
ISocketConnection connection,
OperationRequestBuilder requestBuilder,
CancellationToken cancellationToken)
{
// keeping these lines is important!
await base.OnRequestAsync(
connection,
requestBuilder,
cancellationToken);
requestBuilder.SetServices(YOUR_SERVICE_PROVIDER);
}
}

Register these interceptors for them to take effect:

C#
builder
.AddGraphQL()
.AddHttpRequestInterceptor<HttpRequestInterceptor>()
.AddSocketSessionInterceptor<SocketSessionInterceptor>();

Learn more about interceptors

Next Steps

Last updated on April 13, 2026 by Michael Staib