Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ 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: |
out/*
- 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: |
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ name: Run code tests

on:
pull_request:
branches:
- master
- develop
push:
branches:
- master
Expand Down Expand Up @@ -54,7 +51,7 @@ 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
path: |
Expand Down
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