diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4a16dce..01cabe8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,7 +27,7 @@ jobs:
- name: Pack solution [Release]
run: dotnet pack --no-restore --no-build -c Release -p:VersionSuffix=$GITHUB_RUN_NUMBER -o out
- name: Upload artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: Nuget packages
path: |
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 7fc3752..966a64e 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -37,7 +37,7 @@ jobs:
- name: Pack solution [Release]
run: dotnet pack --no-restore --no-build -c Release -p:Version=$version -o out
- name: Upload artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: Nuget packages
path: |
@@ -45,7 +45,7 @@ jobs:
- name: Publish Nuget packages to Nuget registry
run: dotnet nuget push "out/*" -k ${{secrets.NUGET_AUTH_TOKEN}}
- name: Upload nuget packages as release artifacts
- uses: actions/github-script@v2
+ uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0aa1866..578dfb8 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,9 +2,6 @@ name: Run code tests
on:
pull_request:
- branches:
- - master
- - develop
push:
branches:
- master
@@ -54,9 +51,9 @@ jobs:
reporttypes: 'Html'
tag: 'test_${{ github.run_number }}'
- name: Upload artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
- name: Code coverage artifacts
+ name: Code coverage artifacts for ${{ matrix.os }}
path: |
${{ matrix.os }}.lcov.info
Clover.xml
diff --git a/Directory.Build.props b/Directory.Build.props
index a98330b..3e4a590 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -24,6 +24,7 @@
Recommended8.0.06.0.0
+ direct
diff --git a/src/GraphQL.DI/DIObjectGraphBase.cs b/src/GraphQL.DI/DIObjectGraphBase.cs
index 67e2db3..8ce896b 100644
--- a/src/GraphQL.DI/DIObjectGraphBase.cs
+++ b/src/GraphQL.DI/DIObjectGraphBase.cs
@@ -9,6 +9,12 @@ namespace GraphQL.DI;
///
/// This is a required base type of all DI-created graph types. may be
/// used if the type is .
+///
+/// If the derived class implements , the class must be registered within the DI container.
+///
+///
+/// When registered within the DI container, the service lifetime must be Transient.
+///
///
public abstract class DIObjectGraphBase : IDIObjectGraphBase, IResolveFieldContext
{
diff --git a/src/GraphQL.DI/DIObjectGraphType.cs b/src/GraphQL.DI/DIObjectGraphType.cs
index 2d0acc3..a156165 100644
--- a/src/GraphQL.DI/DIObjectGraphType.cs
+++ b/src/GraphQL.DI/DIObjectGraphType.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using GraphQL.Types;
@@ -60,13 +61,36 @@ protected override IEnumerable GetRegisteredMembers()
// each field resolver will build a new instance of DIObject
///
protected override LambdaExpression BuildMemberInstanceExpression(MemberInfo memberInfo)
- => (Expression>)((IResolveFieldContext context) => MemberInstanceFunc(context));
+ {
+ // use an explicit type here rather than simply LambdaExpression
+ Expression> func;
+ if (typeof(IDisposable).IsAssignableFrom(typeof(TDIGraph))) {
+ func = (IResolveFieldContext context) => MemberInstanceDisposableFunc(context);
+ } else {
+ func = (IResolveFieldContext context) => MemberInstanceFunc(context);
+ }
+ return func;
+ }
///
private static TDIGraph MemberInstanceFunc(IResolveFieldContext context)
{
// create a new instance of DIObject, filling in any constructor arguments from DI
- var graph = ActivatorUtilities.GetServiceOrCreateInstance(context.RequestServices ?? throw new MissingRequestServicesException());
+ var graph = ActivatorUtilities.GetServiceOrCreateInstance(context.RequestServices ?? ThrowMissingRequestServicesException());
+ // set the context
+ graph.Context = context;
+ // return the object
+ return graph;
+
+ static IServiceProvider ThrowMissingRequestServicesException() => throw new MissingRequestServicesException();
+ }
+
+ ///
+ private static TDIGraph MemberInstanceDisposableFunc(IResolveFieldContext context)
+ {
+ // pull DIObject from dependency injection, as it is disposable and must be managed by DI
+ var graph = (context.RequestServices ?? throw new MissingRequestServicesException()).GetService()
+ ?? throw new InvalidOperationException($"Could not resolve an instance of {typeof(TDIGraph).Name} from the service provider. DI graph types that implement IDisposable must be registered in the service provider.");
// set the context
graph.Context = context;
// return the object
diff --git a/src/GraphQL.DI/GraphQLBuilderExtensions.cs b/src/GraphQL.DI/GraphQLBuilderExtensions.cs
index 9c694a1..29f44f8 100644
--- a/src/GraphQL.DI/GraphQLBuilderExtensions.cs
+++ b/src/GraphQL.DI/GraphQLBuilderExtensions.cs
@@ -18,6 +18,26 @@ public static IGraphQLBuilder AddDIGraphTypes(this IGraphQLBuilder builder)
return builder;
}
+ ///
+ /// Scans the calling assembly for classes that implement
+ /// and registers them as transients within the DI container.
+ ///
+ public static IGraphQLBuilder AddDIGraphBases(this IGraphQLBuilder builder)
+ => AddDIGraphBases(builder, Assembly.GetCallingAssembly());
+
+ ///
+ /// Scans the specified assembly for classes that implement
+ /// and registers them as transients within the DI container.
+ ///
+ public static IGraphQLBuilder AddDIGraphBases(this IGraphQLBuilder builder, Assembly assembly)
+ {
+ foreach (var type in assembly.GetTypes()
+ .Where(x => x.IsClass && !x.IsAbstract && typeof(IDIObjectGraphBase).IsAssignableFrom(x))) {
+ builder.Services.TryRegister(type, type, ServiceLifetime.Transient);
+ }
+ return builder;
+ }
+
///
/// Scans the calling assembly for classes that implement and
/// registers clr type mappings on the schema between that
diff --git a/src/GraphQL.DI/GraphQLDIBuilderExtensions.cs b/src/GraphQL.DI/GraphQLDIBuilderExtensions.cs
new file mode 100644
index 0000000..9cd5110
--- /dev/null
+++ b/src/GraphQL.DI/GraphQLDIBuilderExtensions.cs
@@ -0,0 +1,63 @@
+using System.Reflection;
+using GraphQL.DI;
+
+namespace GraphQL;
+
+///
+/// Provides extension methods to configure GraphQL.NET services within a dependency injection framework.
+///
+public static class GraphQLDIBuilderExtensions
+{
+ ///
+ /// Performs the following:
+ ///
+ ///
+ /// Registers and
+ /// as generic types.
+ ///
+ ///
+ /// Scans the calling assembly for classes that implement
+ /// and registers them as transients within the DI container.
+ ///
+ ///
+ /// Scans the calling assembly for classes that implement and
+ /// registers clr type mappings on the schema between that
+ /// (constructed from that class and its source type), and the source type.
+ /// Skips classes where the source type is , or where the class is marked with
+ /// the , or where another graph type would be automatically mapped
+ /// to the specified type, or where a graph type has already been registered to the specified clr type.
+ ///
+ ///
+ ///
+ public static IGraphQLBuilder AddDI(this IGraphQLBuilder builder)
+ => AddDI(builder, Assembly.GetCallingAssembly());
+
+ ///
+ /// Performs the following:
+ ///
+ ///
+ /// Registers and
+ /// as generic types.
+ ///
+ ///
+ /// Scans the specified assembly for classes that implement
+ /// and registers them as transients within the DI container.
+ ///
+ ///
+ /// Scans the specified assembly for classes that implement and
+ /// registers clr type mappings on the schema between that
+ /// (constructed from that class and its source type), and the source type.
+ /// Skips classes where the source type is , or where the class is marked with
+ /// the , or where another graph type would be automatically mapped
+ /// to the specified type, or where a graph type has already been registered to the specified clr type.
+ ///
+ ///
+ ///
+ public static IGraphQLBuilder AddDI(this IGraphQLBuilder builder, Assembly assembly)
+ {
+ return builder
+ .AddDIGraphTypes()
+ .AddDIGraphBases(assembly)
+ .AddDIClrTypeMappings(assembly);
+ }
+}
diff --git a/src/Tests/DIObjectGraphTypeTests/DIObjectGraphTypeTestBase.cs b/src/Tests/DIObjectGraphTypeTests/DIObjectGraphTypeTestBase.cs
index 49feaa7..1e959cc 100644
--- a/src/Tests/DIObjectGraphTypeTests/DIObjectGraphTypeTestBase.cs
+++ b/src/Tests/DIObjectGraphTypeTests/DIObjectGraphTypeTestBase.cs
@@ -31,13 +31,17 @@ public DIObjectGraphTypeTestBase() : base()
_contextMock.SetupGet(x => x.Schema).Returns((ISchema)null!);
}
- protected IComplexGraphType Configure(bool instance = false, bool scoped = false) where T : DIObjectGraphBase, new()
+ protected IComplexGraphType Configure(bool instance = false, bool scoped = false, bool registered = true) where T : DIObjectGraphBase, new()
{
if (instance) {
- if (scoped) {
- _scopedServiceProviderMock.Setup(x => x.GetService(typeof(T))).Returns(() => new T()).Verifiable();
+ if (registered) {
+ if (scoped) {
+ _scopedServiceProviderMock.Setup(x => x.GetService(typeof(T))).Returns(() => new T()).Verifiable();
+ } else {
+ _serviceProviderMock.Setup(x => x.GetService(typeof(T))).Returns(() => new T()).Verifiable();
+ }
} else {
- _serviceProviderMock.Setup(x => x.GetService(typeof(T))).Returns(() => new T()).Verifiable();
+ _serviceProviderMock.Setup(x => x.GetService(typeof(T))).Returns(() => null).Verifiable();
}
}
_graphType = new DIObjectGraphType();
diff --git a/src/Tests/DIObjectGraphTypeTests/Field.cs b/src/Tests/DIObjectGraphTypeTests/Field.cs
index ba6c0c5..a7bcb0a 100644
--- a/src/Tests/DIObjectGraphTypeTests/Field.cs
+++ b/src/Tests/DIObjectGraphTypeTests/Field.cs
@@ -19,10 +19,12 @@ public class CStaticMethod : DIObjectGraphBase
public static string? Field1() => "hello";
}
- [Fact]
- public void InstanceMethod()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void InstanceMethod(bool registered)
{
- Configure(true);
+ Configure(true, registered: registered);
VerifyField("Field1", nullable: true, concurrent: false, returnValue: "hello");
Verify(false);
}
@@ -443,6 +445,31 @@ public class CIgnore : DIObjectGraphBase