Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add support for IDisposable
  • Loading branch information
Shane32 committed Dec 11, 2024
commit 83afa06ec9911ece3d7c37d052441d4152d223bd
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<AnalysisMode>Recommended</AnalysisMode>
<GraphQLVersion>8.0.0</GraphQLVersion>
<GraphQLAspNetCore3Version>6.0.0</GraphQLAspNetCore3Version>
<NuGetAuditMode>direct</NuGetAuditMode>
</PropertyGroup>

<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions src/GraphQL.DI/DIObjectGraphBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ namespace GraphQL.DI;
/// <summary>
/// This is a required base type of all DI-created graph types. <see cref="DIObjectGraphBase"/> may be
/// used if the <see cref="IResolveFieldContext.Source"/> type is <see cref="object"/>.
/// <para>
/// If the derived class implements <see cref="IDisposable"/>, the class must be registered within the DI container.
/// </para>
/// <para>
/// When registered within the DI container, the service lifetime must be <see cref="Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient">Transient</see>.
/// </para>
/// </summary>
public abstract class DIObjectGraphBase<TSource> : IDIObjectGraphBase<TSource>, IResolveFieldContext<TSource>
{
Expand Down
28 changes: 26 additions & 2 deletions src/GraphQL.DI/DIObjectGraphType.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using GraphQL.Types;
Expand Down Expand Up @@ -60,13 +61,36 @@ protected override IEnumerable<MemberInfo> GetRegisteredMembers()
// each field resolver will build a new instance of DIObject
/// <inheritdoc/>
protected override LambdaExpression BuildMemberInstanceExpression(MemberInfo memberInfo)
=> (Expression<Func<IResolveFieldContext, TDIGraph>>)((IResolveFieldContext context) => MemberInstanceFunc(context));
{
// use an explicit type here rather than simply LambdaExpression
Expression<Func<IResolveFieldContext, TDIGraph>> func;
if (typeof(IDisposable).IsAssignableFrom(typeof(TDIGraph))) {
func = (IResolveFieldContext context) => MemberInstanceDisposableFunc(context);
} else {
func = (IResolveFieldContext context) => MemberInstanceFunc(context);
}
return func;
}

/// <inheritdoc/>
private static TDIGraph MemberInstanceFunc(IResolveFieldContext context)
{
// create a new instance of DIObject, filling in any constructor arguments from DI
var graph = ActivatorUtilities.GetServiceOrCreateInstance<TDIGraph>(context.RequestServices ?? throw new MissingRequestServicesException());
var graph = ActivatorUtilities.GetServiceOrCreateInstance<TDIGraph>(context.RequestServices ?? ThrowMissingRequestServicesException());
// set the context
graph.Context = context;
// return the object
return graph;

static IServiceProvider ThrowMissingRequestServicesException() => throw new MissingRequestServicesException();
}

/// <inheritdoc/>
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<TDIGraph>()
?? 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
Expand Down
20 changes: 20 additions & 0 deletions src/GraphQL.DI/GraphQLBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ public static IGraphQLBuilder AddDIGraphTypes(this IGraphQLBuilder builder)
return builder;
}

/// <summary>
/// Scans the calling assembly for classes that implement <see cref="IDIObjectGraphBase{TSource}"/>
/// and registers them as transients within the DI container.
/// </summary>
public static IGraphQLBuilder AddDIGraphBases(this IGraphQLBuilder builder)
=> AddDIGraphBases(builder, Assembly.GetCallingAssembly());

/// <summary>
/// Scans the specified assembly for classes that implement <see cref="IDIObjectGraphBase"/>
/// and registers them as transients within the DI container.
/// </summary>
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;
}

/// <summary>
/// Scans the calling assembly for classes that implement <see cref="IDIObjectGraphBase{TSource}"/> and
/// registers clr type mappings on the schema between that <see cref="DIObjectGraphType{TDIGraph, TSource}"/>
Expand Down
63 changes: 63 additions & 0 deletions src/GraphQL.DI/GraphQLDIBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Reflection;
using GraphQL.DI;

namespace GraphQL;

/// <summary>
/// Provides extension methods to configure GraphQL.NET services within a dependency injection framework.
/// </summary>
public static class GraphQLDIBuilderExtensions
{
/// <summary>
/// Performs the following:
/// <list type="bullet">
/// <item>
/// Registers <see cref="DIObjectGraphType{TDIGraph}"/> and
/// <see cref="DIObjectGraphType{TDIGraph, TSource}"/> as generic types.
/// </item>
/// <item>
/// Scans the calling assembly for classes that implement <see cref="IDIObjectGraphBase{TSource}"/>
/// and registers them as transients within the DI container.
/// </item>
/// <item>
/// Scans the calling assembly for classes that implement <see cref="IDIObjectGraphBase{TSource}"/> and
/// registers clr type mappings on the schema between that <see cref="DIObjectGraphType{TDIGraph, TSource}"/>
/// (constructed from that class and its source type), and the source type.
/// Skips classes where the source type is <see cref="object"/>, or where the class is marked with
/// the <see cref="DoNotMapClrTypeAttribute"/>, 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.
/// </item>
/// </list>
/// </summary>
public static IGraphQLBuilder AddDI(this IGraphQLBuilder builder)
=> AddDI(builder, Assembly.GetCallingAssembly());

/// <summary>
/// Performs the following:
/// <list type="bullet">
/// <item>
/// Registers <see cref="DIObjectGraphType{TDIGraph}"/> and
/// <see cref="DIObjectGraphType{TDIGraph, TSource}"/> as generic types.
/// </item>
/// <item>
/// Scans the specified assembly for classes that implement <see cref="IDIObjectGraphBase{TSource}"/>
/// and registers them as transients within the DI container.
/// </item>
/// <item>
/// Scans the specified assembly for classes that implement <see cref="IDIObjectGraphBase{TSource}"/> and
/// registers clr type mappings on the schema between that <see cref="DIObjectGraphType{TDIGraph, TSource}"/>
/// (constructed from that class and its source type), and the source type.
/// Skips classes where the source type is <see cref="object"/>, or where the class is marked with
/// the <see cref="DoNotMapClrTypeAttribute"/>, 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.
/// </item>
/// </list>
/// </summary>
public static IGraphQLBuilder AddDI(this IGraphQLBuilder builder, Assembly assembly)
{
return builder
.AddDIGraphTypes()
.AddDIGraphBases(assembly)
.AddDIClrTypeMappings(assembly);
}
}
Loading