Skip to content

Commit

Permalink
Add Context to DIObjectGraphBase; use GetOrCreate when creating insta…
Browse files Browse the repository at this point in the history
…nces (#6)
  • Loading branch information
Shane32 committed Jul 20, 2021
1 parent addd436 commit 3fd5ed5
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 4 deletions.
50 changes: 48 additions & 2 deletions src/GraphQL.DI/DIObjectGraphBase.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,68 @@
using System;
using System.Collections.Generic;
using System.Threading;
using GraphQL.Execution;
using GraphQL.Instrumentation;
using GraphQL.Language.AST;
using GraphQL.Types;

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"/>.
/// </summary>
//this class is a placeholder for future support of properties or methods on the base class
public class DIObjectGraphBase<TSource> : IDIObjectGraphBase<TSource>
public abstract class DIObjectGraphBase<TSource> : IDIObjectGraphBase<TSource>, IResolveFieldContext<TSource>
{
//this would be an ideal spot to put public readonly fields for the resolving query, such as Schema, Metrics, Executor, and so on, rather than being inside the ResolveFieldContext instance.
//this could only contain fields that are not unique to a resolving field (such as Source), so as to not break multithreading support
//with DI, any objects necessary could be brought in via dependency injection (such as Schema), so they really don't need to be in here

/// <summary>
/// The <see cref="IResolveFieldContext"/> for the current field.
/// </summary>
public IResolveFieldContext Context { get; private set; } = null!;

/// <inheritdoc cref="IResolveFieldContext.Source"/>
public TSource Source => (TSource)Context.Source;

/// <inheritdoc cref="IResolveFieldContext.CancellationToken"/>
public CancellationToken RequestAborted => Context.CancellationToken;

/// <inheritdoc cref="IProvideUserContext.UserContext"/>
public IDictionary<string, object> UserContext => Context.UserContext;

/// <inheritdoc cref="IResolveFieldContext.Metrics"/>
public Metrics Metrics => Context.Metrics;

IResolveFieldContext IDIObjectGraphBase.Context { get => Context; set => Context = value; }
Field IResolveFieldContext.FieldAst => Context.FieldAst;
FieldType IResolveFieldContext.FieldDefinition => Context.FieldDefinition;
IObjectGraphType IResolveFieldContext.ParentType => Context.ParentType;
IResolveFieldContext IResolveFieldContext.Parent => Context.Parent;
IDictionary<string, ArgumentValue> IResolveFieldContext.Arguments => Context.Arguments;
object IResolveFieldContext.RootValue => Context.RootValue;
object IResolveFieldContext.Source => Context.Source;
ISchema IResolveFieldContext.Schema => Context.Schema;
Document IResolveFieldContext.Document => Context.Document;
Operation IResolveFieldContext.Operation => Context.Operation;
Variables IResolveFieldContext.Variables => Context.Variables;
CancellationToken IResolveFieldContext.CancellationToken => Context.CancellationToken;
ExecutionErrors IResolveFieldContext.Errors => Context.Errors;
IEnumerable<object> IResolveFieldContext.Path => Context.Path;
IEnumerable<object> IResolveFieldContext.ResponsePath => Context.ResponsePath;
Dictionary<string, Field> IResolveFieldContext.SubFields => Context.SubFields;
IDictionary<string, object> IResolveFieldContext.Extensions => Context.Extensions;
IServiceProvider IResolveFieldContext.RequestServices => Context.RequestServices;
IExecutionArrayPool IResolveFieldContext.ArrayPool => Context.ArrayPool;
}

/// <summary>
/// This is a required base type of all DI-created graph types. <see cref="DIObjectGraphBase{TSource}"/> may be
/// used when the <see cref="IResolveFieldContext.Source"/> type is not <see cref="object"/>.
/// </summary>
public class DIObjectGraphBase : DIObjectGraphBase<object>, IDIObjectGraphBase<object>
public abstract class DIObjectGraphBase : DIObjectGraphBase<object>, IDIObjectGraphBase<object>
{
}
}
19 changes: 17 additions & 2 deletions src/GraphQL.DI/DIObjectGraphType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ public DIObjectGraphType()

//grab some methods via reflection for us to use later
private static readonly MethodInfo _getRequiredServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static).Single(x => x.Name == nameof(ServiceProviderServiceExtensions.GetRequiredService) && !x.IsGenericMethod);
private static readonly MethodInfo _getOrCreateServiceMethod = typeof(ActivatorUtilities).GetMethods(BindingFlags.Public | BindingFlags.Static).Single(x => x.Name == nameof(ActivatorUtilities.GetServiceOrCreateInstance) && !x.IsGenericMethod);
private static readonly MethodInfo _asMethod = typeof(ResolveFieldContextExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public).Single(x => x.Name == nameof(ResolveFieldContextExtensions.As) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(IResolveFieldContext));
private static readonly MethodInfo _getArgumentMethod = typeof(ResolveFieldContextExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static).Single(x => x.Name == nameof(ResolveFieldContextExtensions.GetArgument) && x.IsGenericMethod);
private static readonly MethodInfo _setContextMethod = typeof(IDIObjectGraphBase).GetProperty(nameof(IDIObjectGraphBase.Context)).GetSetMethod();
private static readonly PropertyInfo _sourceProperty = typeof(IResolveFieldContext).GetProperty(nameof(IResolveFieldContext.Source), BindingFlags.Instance | BindingFlags.Public);
private static readonly PropertyInfo _requestServicesProperty = typeof(IResolveFieldContext).GetProperty(nameof(IResolveFieldContext.RequestServices), BindingFlags.Instance | BindingFlags.Public);
private static readonly PropertyInfo _cancellationTokenProperty = typeof(IResolveFieldContext).GetProperty(nameof(IResolveFieldContext.CancellationToken), BindingFlags.Public | BindingFlags.Instance);
Expand Down Expand Up @@ -374,11 +376,24 @@ protected virtual Type InferOutputGraphType(Type type, bool nullable)
/// <summary>
/// Returns an expression that gets/creates an instance of <typeparamref name="TDIGraph"/> from a <see cref="IResolveFieldContext"/>.
/// Defaults to the following:
/// <code>context =&gt; context.RequestServices.GetRequiredService&lt;TDIGraph&gt;();</code>
/// <code>context =&gt; {<br/>
/// var g = ActivatorUtilities.GetServiceOrCreateInstance&lt;T&gt;(context.RequestServices);<br/>
/// ((IDIObjectGraphBase)g).Context = context;<br/>
/// return g;<br/>
/// }</code>
/// </summary>
protected virtual Expression GetInstanceExpression(ParameterExpression resolveFieldContextParameter)
{
return GetServiceExpression(resolveFieldContextParameter, typeof(TDIGraph));
var serviceType = typeof(TDIGraph);
var serviceProvider = GetServiceProviderExpression(resolveFieldContextParameter);
var type = Expression.Constant(serviceType ?? throw new ArgumentNullException(nameof(serviceType)));
var getService = Expression.Call(_getOrCreateServiceMethod, serviceProvider, type);
var cast = Expression.Convert(getService, serviceType);
var variable = Expression.Parameter(serviceType);
var setVariable = Expression.Assign(variable, cast);
var setContext = Expression.Call(variable, _setContextMethod, resolveFieldContextParameter);
var block = Expression.Block(serviceType, new ParameterExpression[] { variable }, new Expression[] { setVariable, setContext, variable });
return block;
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/GraphQL.DI/IDIObjectGraphBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace GraphQL.DI
{
public interface IDIObjectGraphBase
{
public IResolveFieldContext Context { get; set; }
}

public interface IDIObjectGraphBase<TSource> : IDIObjectGraphBase
Expand Down
237 changes: 237 additions & 0 deletions src/Tests/DIObjectGraphBaseTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using GraphQL;
using GraphQL.DI;
using GraphQL.Execution;
using GraphQL.Instrumentation;
using GraphQL.Language.AST;
using GraphQL.Types;
using Moq;
using Shouldly;
using Xunit;

namespace Tests
{
public class DIObjectGraphBaseTests
{
private readonly DIObjectGraphBase _graph = new CTest();
private IResolveFieldContext _graphContext => (IResolveFieldContext)_graph;
private readonly Mock<IResolveFieldContext> _mockContext = new Mock<IResolveFieldContext>(MockBehavior.Strict);
private IResolveFieldContext _context => _mockContext.Object;

public DIObjectGraphBaseTests()
{
((IDIObjectGraphBase)_graph).Context = _context;
((IDIObjectGraphBase)_graph).Context.ShouldBe(_context);
}

[Fact]
public void Context()
{
_graph.Context.ShouldBe(_context);
}

[Fact]
public void RequestAborted()
{
var token = new CancellationTokenSource().Token;
_mockContext.Setup(x => x.CancellationToken).Returns(token);
_graph.RequestAborted.ShouldBe(token);
}

[Fact]
public void Metrics()
{
var metrics = new Metrics();
_mockContext.Setup(x => x.Metrics).Returns(metrics);
_graph.Metrics.ShouldBe(metrics);
}

[Fact]
public void UserContext()
{
var userContext = new Dictionary<string, object>();
_mockContext.Setup(x => x.UserContext).Returns(userContext);
_graph.UserContext.ShouldBe(userContext);
}

[Fact]
public void Source()
{
var source = new object();
_mockContext.Setup(x => x.Source).Returns(source);
_graph.Source.ShouldBe(source);
}

[Fact]
public void SourceTyped()
{
_mockContext.Setup(x => x.Source).Returns(3);
var graph = new CTest<int>();
((IDIObjectGraphBase)graph).Context = _context;
graph.Source.ShouldBe(3);
}

[Fact]
public void RFC_FieldAst()
{
var fieldAst = new Field(default, default);
_mockContext.Setup(x => x.FieldAst).Returns(fieldAst);
_graphContext.FieldAst.ShouldBe(fieldAst);
}

[Fact]
public void RFC_FieldDefinition()
{
var fieldDef = new FieldType();
_mockContext.Setup(x => x.FieldDefinition).Returns(fieldDef);
_graphContext.FieldDefinition.ShouldBe(fieldDef);
}

[Fact]
public void RFC_ParentType()
{
var obj = Mock.Of<IObjectGraphType>();
_mockContext.Setup(x => x.ParentType).Returns(obj);
_graphContext.ParentType.ShouldBe(obj);
}

[Fact]
public void RFC_Parent()
{
var obj = Mock.Of<IResolveFieldContext>();
_mockContext.Setup(x => x.Parent).Returns(obj);
_graphContext.Parent.ShouldBe(obj);
}

[Fact]
public void RFC_Arguments()
{
var obj = Mock.Of<IDictionary<string, ArgumentValue>>();
_mockContext.Setup(x => x.Arguments).Returns(obj);
_graphContext.Arguments.ShouldBe(obj);
}

[Fact]
public void RFC_RootValue()
{
var obj = new object();
_mockContext.Setup(x => x.RootValue).Returns(obj);
_graphContext.RootValue.ShouldBe(obj);
}

[Fact]
public void RFC_Source()
{
var obj = new object();
_mockContext.Setup(x => x.Source).Returns(obj);
_graphContext.Source.ShouldBe(obj);
}

[Fact]
public void RFC_Schema()
{
var obj = Mock.Of<ISchema>();
_mockContext.Setup(x => x.Schema).Returns(obj);
_graphContext.Schema.ShouldBe(obj);
}

[Fact]
public void RFC_Document()
{
var obj = new Document();
_mockContext.Setup(x => x.Document).Returns(obj);
_graphContext.Document.ShouldBe(obj);
}

[Fact]
public void RFC_Operation()
{
var obj = new Operation(default);
_mockContext.Setup(x => x.Operation).Returns(obj);
_graphContext.Operation.ShouldBe(obj);
}

[Fact]
public void RFC_Variables()
{
var obj = new Variables();
_mockContext.Setup(x => x.Variables).Returns(obj);
_graphContext.Variables.ShouldBe(obj);
}

[Fact]
public void RFC_CancellationToken()
{
var obj = new CancellationTokenSource().Token;
_mockContext.Setup(x => x.CancellationToken).Returns(obj);
_graphContext.CancellationToken.ShouldBe(obj);
}

[Fact]
public void RFC_Errors()
{
var obj = new ExecutionErrors();
_mockContext.Setup(x => x.Errors).Returns(obj);
_graphContext.Errors.ShouldBe(obj);
}

[Fact]
public void RFC_Path()
{
var obj = Mock.Of<IEnumerable<object>>();
_mockContext.Setup(x => x.Path).Returns(obj);
Assert.StrictEqual(_graphContext.Path, obj);
}

[Fact]
public void RFC_ResponsePath()
{
var obj = Mock.Of<IEnumerable<object>>();
_mockContext.Setup(x => x.ResponsePath).Returns(obj);
Assert.StrictEqual(_graphContext.ResponsePath, obj);
}

[Fact]
public void RFC_SubFields()
{
var obj = new Dictionary<string, Field>();
_mockContext.Setup(x => x.SubFields).Returns(obj);
_graphContext.SubFields.ShouldBe(obj);
}

[Fact]
public void RFC_Extensions()
{
var obj = Mock.Of<IDictionary<string, object>>();
_mockContext.Setup(x => x.Extensions).Returns(obj);
_graphContext.Extensions.ShouldBe(obj);
}

[Fact]
public void RFC_RequestServices()
{
var obj = Mock.Of<IServiceProvider>();
_mockContext.Setup(x => x.RequestServices).Returns(obj);
_graphContext.RequestServices.ShouldBe(obj);
}

[Fact]
public void RFC_ArrayPool()
{
var obj = Mock.Of<IExecutionArrayPool>();
_mockContext.Setup(x => x.ArrayPool).Returns(obj);
_graphContext.ArrayPool.ShouldBe(obj);
}

private class CTest : DIObjectGraphBase
{
}

private class CTest<T> : DIObjectGraphBase<T>
{
}
}
}
12 changes: 12 additions & 0 deletions src/Tests/DIObjectGraphTypeTests/Field.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ public class CInstanceMethod : DIObjectGraphBase
public string Field1() => "hello";
}

[Fact]
public void SetsContext()
{
Configure<CVerifyContext, object>(true);
_graphType.Fields.Find("Field1").Resolver.Resolve(_contextMock.Object).ShouldBe(_contextMock.Object);
}

public class CVerifyContext : DIObjectGraphBase
{
public object Field1() => Context;
}

[Fact]
public async Task StaticAsyncMethod()
{
Expand Down

0 comments on commit 3fd5ed5

Please sign in to comment.