Dependency Injection

GraphQL.NET supports dependency injection through a IServiceProvider interface that is passed to the Schema class. Internally when trying to resolve a type the library will call the methods on this interface.

The library resolves a GraphType only once and caches that type for the lifetime of the Schema.

The default implementation of IServiceProvider uses Activator.CreateInstance. Activator.CreateInstance requires that an object have a public parameterless constructor.

public sealed class DefaultServiceProvider : IServiceProvider
{
    public object GetService(Type serviceType)
    {
        if (serviceType == null)
            throw new ArgumentNullException(nameof(serviceType));

        try
        {
            return Activator.CreateInstance(serviceType);
        }
        catch (Exception exception)
        {
            throw new Exception($"Failed to call Activator.CreateInstance. Type: {serviceType.FullName}", exception);
        }
    }
}

You can override the default implementation by passing a IServiceProvider to the constructor of your Schema.

public class StarWarsSchema : GraphQL.Types.Schema
{
    public StarWarsSchema(IServiceProvider provider)
        : base(provider)
    {
        Query = provider.GetRequiredService<StarWarsQuery>();
        Mutation = provider.GetRequiredService<StarWarsMutation>();
    }
}

How you integrate this into your system will depend on the dependency injection framework you are using. FuncServiceProvider is provided for easy integration with multiple containers.

Dependency Injection Registration Helpers

GraphQL.NET provides an IGraphQLBuilder interface which encapsulates the configuration methods of a dependency injection framework, to provide an abstract method of configuring a dependency injection framework to work with GraphQL.NET. This interface is returned from a DI-provider-specific setup method (typically called AddGraphQL()), at which point you can call extension methods on the interface to configure this library. A simple example is below:

services.AddGraphQL()
    .AddSystemTextJson()
    .AddSchema<MySchema>();

The interface also allows configuration of the schema during initialization, and configuration of the execution at runtime. In this manner, adding middleware, for example, is as simple as calling .AddMiddleware<MyMiddlware>() and does not require the middleware to be added into the schema configuration.

The AddGraphQL() method will register default implementations of the following services within the dependency injection framework:

  • IDocumentExecuter - which does not support subscriptions
  • IDocumentBuilder
  • IDocumentValidator
  • IComplexityAnalyzer - which is not used unless configured within ExecutionOptions
  • IErrorInfoProvider
  • IDocumentCache - an implemenation which does not cache documents

A list of the available extension methods is below:

Method Description / Notes Library
AddClrTypeMappings Scans the specified assembly for graph types intended to represent CLR types and registers them within the schema
AddComplexityAnalyzer Enables the complexity analyzer and configures its options
AddDataLoader Registers classes necessary for data loader support GraphQL.DataLoader
AddDocumentCache<> Registers the specified document caching service
AddDocumentExecuter<> Registers the specified document executer; useful when needed to change the execution strategy utilized
AddDocumentListener<> Registers the specified document listener and configures execution to use it
AddDocumentWriter<> Registers the specified document writer
AddErrorInfoProvider Registers a custom error info provider or configures the default error info provider
AddGraphTypes Scans the specified assembly for graph types and registers them within the DI framework
AddMemoryCache Registers the memory document cache and configures its options GraphQL.MemoryCache
AddMetrics Registers and enables metrics depending on the supplied arguments
AddMiddleware<> Registers the specified middleware and configures it to be installed during schema initialization
AddNewtonsoftJson Registers the document writer that uses Newtonsoft.Json as its underlying JSON serialization engine GraphQL.NewtonsoftJson
AddSchema<> Registers the specified schema
AddSelfActivatingSchema<> Registers the specified schema which will create instances of unregistered graph types during initialization
AddSubscriptionDocumentExecuter Registers the document executer that has subscription support GraphQL.SystemReactive
AddSystemTextJson Registers the document writer that uses System.Text.Json as its underlying JSON serialization engine GraphQL.SystemTextJson
AddValidationRule<> Registers the specified validation rule and configures it to be used at runtime
ConfigureExecution Configures execution options at runtime
ConfigureSchema Configures schema options when the schema is initialized
Configure<TOptions> Used by extension methods to configures an options class within the DI framework
Register Used by extension methods to register services within the DI framework
TryRegister Used by extension methods to register services within the DI framework when they have not already been registered

The above methods will register the specified services typically as singletons unless otherwise specified. Graph types and middleware is registered as transients so that they will match the schema lifetime. So with a singleton schema, all services are effectively singletons.

To use the AddGraphQL method, you will need to install the proper nuget package for your DI provider. See list below:

DI Provider Nuget Package
Microsoft.Extensions.DependencyInjection GraphQL.MicrosoftDI

ASP.NET Core

See this example.

Microsoft.Extensions.DependencyInjection package used in ASP.NET Core already has support for resolving IServiceProvider interface so no additional settings are required - just add your required dependencies:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
    services.AddSingleton<IDocumentWriter, DocumentWriter>();
    services.AddSingleton<StarWarsData>();
    services.AddSingleton<StarWarsQuery>();
    services.AddSingleton<StarWarsMutation>();
    services.AddSingleton<HumanType>();
    services.AddSingleton<HumanInputType>();
    services.AddSingleton<DroidType>();
    services.AddSingleton<CharacterInterface>();
    services.AddSingleton<EpisodeEnum>();
    services.AddSingleton<ISchema, StarWarsSchema>();
}

To avoid having to register all of the individual graph types in your project, you can import the GraphQL.MicrosoftDI NuGet package package and utilize the SelfActivatingServiceProvider wrapper as follows:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ISchema, StarWarsSchema>(services => new StarWarsSchema(new SelfActivatingServiceProvider(services)));
}

If you previously pulled in your query, mutation and/or subscription classes via dependency injection, you will need to manually pull in those dependencies from the SelfActivatingServiceProvider via GetRequiredService as follows:

public class StarWarsSchema : Schema
{
    public StarWarsSchema(IServiceProvider serviceProvider)
        : base(serviceProvider)
    {
        Query = serviceProvider.GetRequiredService<StarWarsQuery>();
        Mutation = serviceProvider.GetRequiredService<StarWarsMutation>();
    }
}

No other graph types will need to be registered. Graph types will only be instantiated once, during schema initialization as usual. Graph types can also pull in any services registered with dependency injection as usual.

Note that if any of the graph types directly or indirectly implement IDisposable, be sure to register those types with your dependency injection provider, or their Dispose methods will not be called. Any dependencies of graph types that implement IDisposable will be disposed of properly, regardless of whether the graph type is registered within the service provider.

You can also use the services.AddGraphTypes() extension method from the server project to scan the calling assembly for classes that implement IGraphType and register them all as singletons within the service provider. For additional options and overloads of this method, see GraphQLBuilderCoreExtensions.cs.

Nancy TinyIoCContainer

protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
    base.ConfigureApplicationContainer(container);

    container.Register((c, overloads) =>
    {
        return new StarWarsSchema(new FuncServiceProvider(c.Resolve));
    });
}

SimpleContainer

var container = new SimpleContainer();
container.Singleton(new StarWarsSchema(new FuncServiceProvider(container.Get)));

Autofac

protected override void Load(ContainerBuilder builder)
{
    builder
      .Register(c => new FuncServiceProvider(c.Resolve<IComponentContext>().Resolve))
      .As<IServiceProvider>()
      .InstancePerDependency();
}

Castle Windsor

public void Install(IWindsorContainer container, IConfigurationStore store)
{
    container.Register(
      Component
        .For<IServiceProvider>()
        .UsingFactoryMethod(k => k.Resolve)
    );
}

Schema Service Lifetime

Most dependency injection frameworks allow for specifying different service lifetimes for different services. Although they may have different names with different frameworks, the three most common lifetimes are as follows:

  • Transient services are created every time they are injected or requested.
  • Scoped services are created per scope. In a web application, every web request creates a new unique service scope. That means scoped services are generally created per web request.
  • Singleton services are created per DI container. That generally means that they are created only one time per application and then used for whole the application life time.

It is highly recommended that the schema is registered as a singleton. As all graph types are constructed at the same time as the schema, all graph types will effectively have a singleton lifetime, regardless of how it is registered with the DI framework. This is most performant appraoch. Having a scoped schema can degrade performance by a huge margin. For instance, even a small schema execution can slow down by 100x, and much more with a large schema.

Scoped lifetime can be used to allow the schema and all its graph types access to the current DI scope. This is not recommended; please see Scoped Services below. With scoped schemas, it is required that all its graph types are registered within the DI framework as scoped or transient services.

Transient lifetime is also not recommended due to performance degradation. For schemas having a transient lifetime, it is required that all its graph types are also registered within the DI framework as transient services.

Scoped services with a singleton schema lifetime

For reasons described above, it is recommended that the schema is registered as a singleton within the dependency injection framework. However, this prevents including scoped services within the constructor of the schema or your custom graph types.

To use scoped services (e.g. HttpContext scoped services in ASP.NET Core) you will need to pass the scoped service provider into the ExecutionOptions.RequestServices property. Then within any field resolver or field middleware, you can access the IResolveFieldContext.RequestServices property to resolve types via the scoped service provider. Typical integration with ASP.NET Core might look like this:

var result = await _executer.ExecuteAsync(options =>
{
    options.Schema = schema;
    options.Query = request.Query;
    options.Inputs = request.Variables.ToInputs();
    options.RequestServices = context.RequestServices;
});

You could then call scoped services from within field resolvers as shown in the following example:

public class StarWarsQuery : ObjectGraphType
{
    public StarWarsQuery()
    {
        Field<DroidType>(
            "hero",
            resolve: context => context.RequestServices.GetRequiredService<IDroidRepo>().GetDroid("R2-D2")
        );
    }
}

Thread safety with scoped services

When using scoped services, be aware that most scoped services are not thread-safe. Therefore you will likely need to use the SerialExecutionStrategy execution strategy, or write code to create a service scope for the duration of the execution of the field resolver that requires a scoped service. For instance, with Entity Framework Core, typically the database context is registered as a scoped service and obtained via dependency injection. To continue to use the database context in the same manner with a singleton schema, you would need to use a serial execution strategy, or create a scope within each field resolver that requires database access, as shown in the following example:

public class StarWarsQuery : ObjectGraphType
{
    public StarWarsQuery()
    {
        Field<DroidType>(
            "hero",
            resolve: context =>
            {
                using var scope = context.RequestServices.CreateScope();
                var services = scope.ServiceProvider;
                return services.GetRequiredService<MyDbContext>().Droids.Find(1);
            }
        );
    }
}

There are classes to assist with this within the GraphQL.MicrosoftDI NuGet package. Sample usage is as follows:

public class MyGraphType : ObjectGraphType<Category>
{
    public MyGraphType()
    {
        Field("Name", context => context.Source.Name);
        Field<ListGraphType<ProductGraphType>>("Products")
            .ResolveScopedAsync((context, serviceProvider) => {
                var db = serviceProvider.GetRequiredService<MyDbContext>();
                return db.Products.Where(x => x.CategoryId == context.Source.Id).ToListAsync();
            });
    }
}

Be aware that using the service locator in this fashion described in this section could be considered an Anti-Pattern. See Service Locator is an Anti-Pattern. However, the performance benefits far outweigh the anti-pattern idealogy.

Within the GraphQL.MicrosoftDI package, there is also a builder approach to adding scoped dependencies. This makes for a concise and declarative approach. Each field clearly states the service it needs and thereby, the anti-pattern argument does not apply anymore.

public class MyGraphType : ObjectGraphType<Category>
{
    public MyGraphType()
    {
        Field("Name", context => context.Source.Name);
        Field<ListGraphType<ProductGraphType>>().Name("Products")
            .Resolve()
            .WithScope() // creates a service scope as described above; not necessary for serial execution
            .WithService<MyDbContext>()
            .ResolveAsync((context, db) => db.Products.Where(x => x.CategoryId == context.Source.Id).ToListAsync());
    }
}

Another approach to resolve scoped services is to use the SteroidsDI project, as described below.

Using SteroidsDI

To use SteroidsDI with ASP.NET Core, add Defer<> and IScopeProvider in your Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    ...

    // Add SteroidsDI Open Generic Defer<> Factory Class
    services.AddDefer();

    // Add SteroidsDI IScopeProvider to use the AspNetCoreHttpScopeProvider
    // which internally uses the IHttpContextAccessor.HttpContext.RequestServices;
    services.AddHttpScope();

    ...
}

Then in your query graph types you can request services using Defer<T> to be injected via DI, which will be evaluated at runtime to their relevant Scoped services, T has been registered with a Scoped DI lifetime:

public class StarWarsQuery : ObjectGraphType
{
  // #1 - Add dependencies using Defer<T>
  public StarWarsQuery(Defer<IDroidRepo> repoFactory)
  {
    Field<DroidType>(
      "hero",

      // #2 Resolve dependencies using current scope provider
      resolve: context => repoFactory.Value.GetDroid("R2-D2")

    );
  }
}
  1. Add Defer<T> to be injected by the dependency injection container. This is a factory which upon calling Defer.Value will resolve the requested service using any currently registered scope provider (e.g. AspNetCoreHttpScopeProvider)
  2. Use the Defer<T> factory class to resolve the requested dependency using any currently registered scope provider. In our case it will attempt to use the IHttpContextAccessor.HttpContext.RequestServices which is the ASP.NET Core Scoped IServiceProvider in order to resolve the dependency.