Directives
Directives are created as instance of DirectiveType
. When adding to target instance of DirectiveInstance
is used. It contains a reference back to the original type.
Following directives are provided as static properties of DirectiveType
Skip = new DirectiveType(
"skip",
new[]
{
DirectiveLocation.FIELD,
DirectiveLocation.FRAGMENT_SPREAD,
DirectiveLocation.INLINE_FRAGMENT
},
new Args
{
{"if", ScalarType.NonNullBoolean}
})
Include = new DirectiveType(
"include",
new[]
{
DirectiveLocation.FIELD,
DirectiveLocation.FRAGMENT_SPREAD,
DirectiveLocation.INLINE_FRAGMENT
},
new Args()
{
{"if", ScalarType.NonNullBoolean}
})
Deprecated = new DirectiveType(
"deprecated",
new[]
{
DirectiveLocation.FIELD_DEFINITION,
DirectiveLocation.ENUM_VALUE
},
new Args()
{
{"reason", ScalarType.String}
})
Create custom directive
Create simple DirectiveType
and apply instance of it to a field and modify resolver logic to execute custom logic if it has the directive present.
This example will require a role from user when trying to resolve a field value
[Fact]
public async Task Authorize_field_directive_sdl()
{
/* Given */
var builder = new SchemaBuilder()
.Sdl(Parser.ParseTypeSystemDocument(@"
directive @authorize(
role: String =""user""
) on FIELD_DEFINITION
type Query {
requiresAdmin: String @authorize(role:""admin"")
requiresUser: String @authorize
}
schema {
query: Query
}
"));
var resolvers = new ObjectTypeMap
{
{
"Query", new FieldResolversMap
{
{"requiresAdmin", context => new ValueTask<IResolverResult>(Resolve.As("Hello Admin!"))},
{"requiresUser", context => new ValueTask<IResolverResult>(Resolve.As("Hello User!"))}
}
}
};
// mock user and user store
var user = new ClaimsPrincipal(new ClaimsIdentity(new []
{
new Claim("role", "user"),
}));
ClaimsPrincipal FetchUser(int id) => user;
/* When */
var schema = SchemaTools.MakeExecutableSchema(
builder,
resolvers,
directives: new Dictionary<string, CreateDirectiveVisitor>
{
// register directive visitor to be used when authorizeType.Name present
["authorize"] = AuthorizeVisitor(FetchUser)
});
var result = await Executor.ExecuteAsync(new ExecutionOptions
{
Document = Parser.ParseDocument(@"{ requiresAdmin requiresUser }"),
Schema = schema
});
/* Then */
result.ShouldMatchJson(@"
{
""data"": {
""requiresAdmin"": null,
""requiresUser"": ""Hello User!""
},
""errors"": [
{
""message"": ""requires admin role. "",
""locations"": [
{
""line"": 1,
""column"": 3
}
],
""path"": [
""requiresAdmin""
],
""extensions"": {
""code"": ""EXCEPTION""
}
}
]
}
");
}
DirectiveVisitor applies the actual middleware to the resolver chain
public static CreateDirectiveVisitor AuthorizeVisitor(Func<int, ClaimsPrincipal> fetchUser)
{
return builder => new DirectiveVisitor
{
FieldDefinition = (directive, fieldDefinition) =>
{
return fieldDefinition.WithResolver(resolver => resolver.Use((context, next) =>
{
var requiredRole = directive.GetArgument<string>("role");
var user = fetchUser(42);
if (!user.HasClaim("role", requiredRole))
throw new Exception(
"requires admin role. "
);
return next(context);
}).Run(fieldDefinition.Resolver));
}
};
}