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 theSchema
.
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, StarWarsQuery query, StarWarsMutation mutation)
: base(provider)
{
Query = query;
Mutation = mutation;
}
}
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.
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 provided through a configuration delegate 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(builder => builder
.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
and IDocumentExecuter<>
IDocumentBuilder
IDocumentValidator
IErrorInfoProvider
IExecutionStrategySelector
These generic graph types are also registered:
EdgeType<>
, ConnectionType<>
, ConnectionType<,>
and PageInfoType
EnumerationGraphType<>
InputObjectGraphType<>
AutoRegisteringInputObjectGraphType<>
AutoRegisteringObjectGraphType<>
AutoRegisteringInterfaceGraphType<>
A list of the available extension methods is below:
Method Description / Notes LibraryAddAutoClrMappings
Configures unmapped CLR types to use auto-registering graph types AddAutoSchema
Registers a schema based on CLR types 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 AddErrorInfoProvider
Registers a custom error info provider or configures the default error info provider AddExecutionStrategy
Registers an ExecutionStrategyRegistration
for the selected execution strategy and operation type AddExecutionStrategySelector
Registers the specified execution strategy selector AddFederation
Registers the federation types and configures the schema to support Apollo Federation AddGraphTypes
Scans the specified assembly for graph types and registers them within the DI framework AddGraphTypeMappingProvider
Registers a graph type mapping provider for unmapped CLR types AddLegacyComplexityAnalyzer
Enables the v7 complexity analyzer and configures its options AddNewtonsoftJson
Registers the serializer that uses Newtonsoft.Json as its underlying JSON serialization engine GraphQL.NewtonsoftJson AddSchema<>
Registers the specified schema AddSchemaVisitor<>
Registers the specified schema visitor and configures it to be used at schema initialization AddSelfActivatingSchema<>
Registers the specified schema which will create instances of unregistered graph types during initialization AddSerializer<>
Registers the specified serializer AddSystemTextJson
Registers the serializer that uses System.Text.Json as its underlying JSON serialization engine GraphQL.SystemTextJson AddUnhandledExceptionHandler
Configures the unhandled exception handler AddValidationRule<>
Registers the specified validation rule and configures it to be used at runtime ConfigureExecution
Configures execution middleware to monitor or modify both options and the result ConfigureExecutionOptions
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 UseApolloTracing
Registers and enables metrics depending on the supplied arguments, and adds Apollo Tracing data to the execution result UseAutomaticPersistedQueries
Enables Automatic Persisted Queries support GraphQL.MemoryCache UseMemoryCache
Registers the memory document cache and configures its options GraphQL.MemoryCache UseMiddleware<>
Registers the specified middleware and configures it to be installed during schema initialization UsePersistedDocuments
Registers the persisted document handler and configures its options UseTelemetry
Creates telemetry events based on the System.Diagnostics.Activity API, primarily for use with OpenTelemetry .NET 5+ ValidateServices
Verifies that all injected services can be created during GraphQL field execution WithTimeout
Configures the execution timeout
The above methods will register the specified services typically as singletons unless otherwise specified. Graph types and middleware are registered as transients so that they will match the schema lifetime. So with a singleton schema, all services are effectively singletons.
Calls to ConfigureExecutionOptions
and methods that start with Add
will execute first, in the order they appear, followed by calls to ConfigureExecution
and methods that start with Use
. The order of the calls may be important. For instance, calling UseMemoryCache
prior to UseAutomaticPersistedQueries
would result in the memory cache being unable to cache any APQ queries.
Custom IGraphQLBuilder
extension methods typically rely on the Services
property of the builder in order to register services with the underlying dependency injection framework. The Services
property returns a IServiceRegister
interface which has these methods:
Register
Registers a service within the DI framework replacing existing registration if needed TryRegister
Registers a service within the DI framework if it has not already been registered
To use the AddGraphQL
method, you will need to install the proper nuget package for your DI provider. See list below:
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<IGraphQLSerializer, GraphQLSerializer>();
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 .AddGraphTypes()
builder method to scan the calling or specified assembly for classes that implement IGraphType
and register them all as transients within the service provider. Mark your class with DoNotRegisterAttribute
if you want to skip registration.
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:
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 approach. 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 lifetimeFor 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.Variables = _serializer.Deserialize<Inputs>(request.Variables);
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").Resolve(context => context.Source.Name);
Field<ListGraphType<ProductGraphType>>("Products")
.ResolveScopedAsync(context => {
var db = context.RequestServices.GetRequiredService<MyDbContext>();
return db.Products.Where(x => x.CategoryId == context.Source.Id).ToListAsync();
});
}
}
In this case context.RequestServices
will be an IServiceProvider
in a newly created scope.
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, when compared to creating a scoped schema.
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 services it needs and thereby, the anti-pattern argument does not apply anymore.
public class MyGraphType : ObjectGraphType<Category>
{
public MyGraphType()
{
Field("Name").Resolve(context => context.Source.Name);
Field<ListGraphType<ProductGraphType>>("Products")
.Resolve()
.WithScope()
.WithService<MyDbContext>()
.ResolveAsync(async (context, db) => await 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 SteroidsDITo use SteroidsDI with ASP.NET Core, add Defer<>
and IScopeProvider
in your Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddDefer();
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
{
public StarWarsQuery(Defer<IDroidRepo> repoFactory)
{
Field<DroidType>("hero")
.Resolve(context => repoFactory.Value.GetDroid("R2-D2"));
}
}
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
)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.RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4