Custom Extensions
Field extensions let you move common patterns or use cases into an extension method to easily apply the logic across many fields. To implement an extension you implement the IFieldExtension
interface and create an extension method. You can have your extension applied via an Attribute
in SchemaBuilder
by having your attribute extend FieldExtensionAttribute
.
Here is an extension method to add a format argument to a field.
public static class UseFormatExtension
{
public static Field UseFormat(this Field field)
{
if (field.Resolve.Type != typeof(string))
throw new ArgumentException($"UseFormat must only be called on a field that returns a string");
// register extension on field
field.AddExtension(new FormatStringExtension());
return field;
}
public class UseFormatAttribute : FieldExtensionAttribute
{
public override void ApplyExtension(Field field)
{
field.UseFormat(DefaultPageSize, MaxPageSize);
}
}
}
FormatStringExtension
needs to implement IFieldExtension
or extend BaseFieldExtension
.
public class FormatStringExtension : IFieldExtension
{
// Configure the field. Do as much as we can here as it is only called once on registered.
public void Configure(ISchemaProvider schema, IField field)
{
}
// This is called on compilation of a query if the query references this field
// Good opportunity to check arguments
// Most often you can update the expression here and return your new one
public Expression GetExpression(Field field, Expression expression, ParameterExpression? argExpression, dynamic? arguments, Expression context, IGraphQLNode? parentNode, bool servicesPass, ParameterReplacer parameterReplacer)
{
if (arguments.last == null && arguments.first == null)
throw new ArgumentException($"Please provide at least the first or last argument");
// build the expression that calls your format logic/method
var call = Expression.Cal(...);
return call;
}
public Expression ProcessScalarExpression(Expression expression, ParameterReplacer parameterReplacer)
{
// Only called for scalar fields (result in data, not a selection)
// called at the final stage just before being used in the final expression for execution
// Often no need to do anything here
return expression;
}
public (Expression baseExpression, Dictionary<IFieldKey, CompiledField> selectionExpressions, ParameterExpression selectContextParam) ProcessExpressionSelection(GraphQLFieldType fieldType, Expression baseExpression, Dictionary<IFieldKey, CompiledField> selectionExpressions, ParameterExpression? selectContextParam, bool servicesPass, ParameterReplacer parameterReplacer)
{
// Called for object projection and collection fields. Giving you an opportunity to modify
// the selection expression or the selection base expression.
// ConnectionEdgeNodeExtension provides a good example of this from UseConnectionPaging
return (baseExpression, selectionExpressions, selectContextParam);
}
}
See FilterExpressionExtension
for a simple example. SortExtension
for a sightly more complex one. Or ConnectionPagingExtension.cs
for an example that changes the shape of the field (from a collection to a Connection object).
If your field extension changes the shape of the original graph (like the 2 paging extensions) you will need to implement GetListExpressionForBulkResolve
to support bulk resolvers on fields within your extension. See ConnectionPagingEdgeExtension
for an example.