Skip to content

Commit b7eda5e

Browse files
v0.10.1 changes (#19)
* Added @specifiedBy directive documentation * Added inheritance sections to OBJECT and INTERFACE pages * Various clean ups and word edits to other pages
1 parent f9d3f19 commit b7eda5e

File tree

11 files changed

+277
-49
lines changed

11 files changed

+277
-49
lines changed

docs/advanced/custom-scalars.md

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ public class GuidScalarSerializer : IScalarValueSerializer
165165
166166
---
167167

168-
The Completed Money Scalar:
168+
### Example: Money Scalar
169+
The completed Money custom scalar type
169170

170171
```csharp
171172

@@ -235,27 +236,60 @@ The last step in declaring a scalar is to register it with the runtime. Scalars
235236
public void ConfigureServices(IServiceCollection services)
236237
{
237238
// register the scalar type to the global provider
238-
// BEFORE a call to .AddGraphQL()
239+
// BEFORE calling .AddGraphQL()
239240
GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(MoneyScalarType));
240241

241-
services.AddMvc()
242-
.AddGraphQL();
242+
services.AddGraphQL();
243243
}
244244
```
245245

246-
Since our scalar is, internally, represented by a class, if we don't pre-register it GraphQL will attempt to parse the `Money` class as an object graph type. Once registered as a scalar, any attempt to use `Money` as an object graph type will cause an exception.
246+
Since our scalar is represented by a .NET class, if we don't pre-register it GraphQL will attempt to parse the `Money` class as an object graph type. Once registered as a scalar, any attempt to use `Money` as an object graph type will cause an exception.
247+
248+
## @specifiedBy Directive
249+
250+
GraphQL provides a special, built-in directive called `@specifiedBy` that allows you to supply a URL pointing to a the specification for your custom scalar. This url is used by various tools to additional data to your customers so they know how to interact with your scalar type. It is entirely optional.
251+
252+
The @specifiedBy directive can be applied to a scalar in all the same ways as other type system directives or by use of the special `[SpecifiedBy]` attribute.
253+
254+
```csharp
255+
// apply the directive to a single schema
256+
GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(MoneyScalarType));
257+
services.AddGraphQL(o => {
258+
o.ApplyDirective("@specifiedBy")
259+
.WithArguments("https://myurl.com")
260+
.ToItems(item => item.Name == "Money");
261+
});
262+
263+
// via the ApplyDirective attribute
264+
// for all schemas
265+
[ApplyDirective("@specifiedBy", "https://myurl.com")]
266+
public class MoneyScalarType : IScalarType
267+
{
268+
// ...
269+
}
270+
271+
// via the special SpecifiedBy attribute
272+
// for all schemas
273+
[SpecifiedBy("https://myurl.com")]
274+
public class MoneyScalarType : IScalarType
275+
{
276+
// ...
277+
}
278+
279+
```
247280

248281
## Tips When Developing a Scalar
249282

250283
A few points about designing your scalar:
251284

285+
- Looking through the [built in scalars](https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Schemas/TypeSystem/Scalars) can be helpful when designing your own.
252286
- Scalar types are expected to be thread safe.
253287
- The runtime will pass a new instance of your scalar graph type to each registered schema. It must be declared with a public, parameterless constructor.
254288
- Scalar types should be simple and work in isolation.
255-
- The `ReadOnlySpan<char>` provided to the `Resolve()` method should be all the data needed to generate a value, there should be no need to perform side effects or fetch additional data.
289+
- The `ReadOnlySpan<char>` provided to `ILeafValueResolver.Resolve` should be all the data needed to generate a value, there should be no need to perform side effects or fetch additional data.
256290
- If you have a lot of logic to unpack a string, consider using a regular OBJECT graph type instead.
257291
- Scalar types should not track any state or depend on any stateful objects.
258-
- `ILeafValueResolver.Resolve` must be **FAST**! Since your resolver is used to construct an initial query plan, it'll be called #orders of magnitude more often than any controller action method.
292+
- `ILeafValueResolver.Resolve` must be **FAST**! Since your resolver is used to construct an initial query plan from a text document, it'll be called orders of magnitude more often than any other method.
259293

260294
### Aim for Fewer Scalars
261295

docs/advanced/directives.md

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ This Directive:
224224

225225
### Example: @deprecated
226226

227+
The @deprecated directive is a built in directive provided by graphql to indication deprecation on a field definition or enum value. Below is the code for its implementation.
228+
227229
```csharp
228230
public sealed class DeprecatedDirective : GraphDirective
229231
{
@@ -435,13 +437,52 @@ type Person {
435437

436438
<br/>
437439

440+
### Repeatable Directives
441+
442+
GraphQL ASP.NET supports repeatable type system directives. Sometimes it can be helpful to apply your directive to an schema item more than once, especially if you would like to supply different parameters on each application.
443+
444+
Add the `[Repeatable]` attribute to the directive definition and you can the apply it multiple times using the standard methods. GraphQL tools that support this new syntax
445+
will be able to properly interprete your schema.
446+
447+
```csharp
448+
// apply repeatable attribute
449+
[Repeatable]
450+
public sealed class ScanItemDirective : GraphDirective
451+
{
452+
[DirectiveLocations(DirectiveLocation.OBJECT)]
453+
public IGraphActionResult Execute(string scanType)
454+
{ /* ... */}
455+
}
456+
457+
// Option 1: Apply the directive to the class directly
458+
[ApplyDirective("@scanItem", "medium")]
459+
[ApplyDirective("@scanItem", "high")]
460+
public class Person
461+
{
462+
}
463+
464+
// Option 2: Apply the directive at startup
465+
services.AddGraphQL(o => {
466+
// ...
467+
o.ApplyDirective("@scanItem")
468+
.WithArguments("medium")
469+
.ToItems(item => item.IsObjectGraphType<Person>());
470+
o.ApplyDirective("@scanItem")
471+
.WithArguments("high")
472+
.ToItems(item => item.IsObjectGraphType<Person>());
473+
});
474+
```
475+
476+
> Order matters. The repeated directives will be executed in the order they are encountered with those applied via attribution taking precedence.
477+
478+
438479
## Directives as Services
439-
Directives are invoked as services through your DI container when they are executed. When you add types to your schema during its initial configuration, GraphQL ASP.NET will automatically register any directives it finds attached to your objects and properties as services in your `IServiceCollection` instance. However, there are times when it cannot do this, such as when you apply a directive by its string declared name. These late-bound directives may still be discoverable later and graphql will attempt to add them to your schema whenever it can. However, it may do this after the opportunity to register them with the DI container has passed.
480+
Directives are invoked as services through your DI container when they are executed. When you add types to your schema during its initial configuration, GraphQL ASP.NET will automatically register any directives it finds attached to your entities as services in your `IServiceCollection` instance. However, there are times when it cannot do this, such as when you apply a directive by its string declared name. These late-bound directives may still be discoverable later and graphql will attempt to add them to your schema whenever it can. However, it may do this after the opportunity to register them with the DI container has passed.
440481

441482
When this occurs, if your directive contains a public, parameterless constructor graphql will still instantiate and use your directive as normal. If the directive contains dependencies in the constructor that it can't resolve, execution of that directive will fail and an exception will be thrown. To be safe, make sure to add any directives you may use to your schema during the `.AddGraphQL()` configuration method. Directives are directly discoverable and will be included via the `options.AddAssembly()` helper method as well.
442483

443484
The benefit of ensuring your directives are part of your `IServiceCollection` should be apparent:
444-
* The directive instance will obey lifetime scopes (transient, scoped, singleton).
485+
* The directive instance will obey lifetime scopes (e.g. transient, scoped, singleton).
445486
* The directive can be instantiated with any dependencies or services you wish; making for a much richer experience.
446487

447488
## Directive Security
@@ -454,16 +495,16 @@ Directives are not considered a layer of security by themselves. Instead, they a
454495
> WARNING: Only use type system directives that you trust. They will always be executed when applied to one or more schema items.
455496

456497
## Understanding the Type System
457-
GraphQL ASP.NET builds your schema and all of its types from your controllers and objects. In general, you do not need to interact with it, however; when applying type system directives you are affecting the final generated schema at run time, making changes as you see fit. When doing this you are forced to interact with the internal type system.
498+
GraphQL ASP.NET builds your schema and all of its types from your controllers and objects. In general, this is done behind the scenes and you do not need to interact with it. However, when applying type system directives you are affecting the final generated schema at run time and need to understand the various parts of it. If you have a question don't be afraid to ask on [github](https://github.com/graphql-aspnet/graphql-aspnet).
458499

459500
**UML Diagrams**
460501

461-
These [uml diagrams](../assets/2022-05-graphql-aspnet-type-system-interface-diagrams.pdf) details the major interfaces and their most useful properties of the type system. However,
502+
These [uml diagrams](../assets/2022-05-graphql-aspnet-type-system-interface-diagrams.pdf) detail the major interfaces and their most useful properties of the type system. However,
462503
these diagrams are not exaustive. Look at the [source code](https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Interfaces/TypeSystem) for the full definitions.
463504
464505
**Helpful Extensions**
465506

466-
There are a robust set of of built in extensions for `ISchemaItem` that can help you filter your data. See the [full source code](https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Configuration/SchemaItemExtensions.cs) for details.
507+
There are a robust set of of built in extensions for `ISchemaItem` that can help you filter your data when applying directives. See the [full source code](https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Configuration/SchemaItemExtensions.cs) for details.
467508
468509
## Demo Project
469510
See the [Demo Projects](../reference/demo-projects.md) page for a demonstration on creating a type system directive for extending a field resolver and an execution directives

docs/advanced/middleware.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,8 @@ The field execution pipeline is executed once for each field of data that needs
140140

141141
1. `ValidateFieldExecutionMiddleware` : Validates that the context and required invocation data has been correctly supplied.
142142
2. `AuthorizeFieldMiddleware` : If the schema is configured for `PerField` authorization this component will invoke the field authorization pipeline for the current field and assign authorization results as appropriate.
143-
3. `InvokeDirectiveResolversMiddleware` : Any directives that were attached to the active field are executed and their results acted on accordingly.
144-
4. `InvokeFieldResolverMiddleware` : The field resolver is called and a data value is created for the active context. This middleware component is ultimately responsible for invoking your controller actions.
145-
5. `ProcessChildFieldsMiddleware` : If any child fields were registered with the invocation context for this field they are dispatched using the context's field result as the new source object.
143+
4. `InvokeFieldResolverMiddleware` : The field resolver is called and a data value is created for the active context. This middleware component is ultimately responsible for invoking your controller actions. It also handles call outs to the directive execution pipeline when required.
144+
4. `ProcessChildFieldsMiddleware` : If any child fields were registered with the invocation context for this field they are dispatched using the context's field result as the new source object.
146145

147146
#### GraphFieldExecutionContext
148147

@@ -165,24 +164,28 @@ public class GraphFieldExecutionContext
165164

166165
The field authorization pipeline can be invoked as part of query execution or field execution depending on your schema's configuration. It contains 1 component:
167166

168-
1. `FieldAuthorizationCheckMiddleware`: Inspects the active `ClaimsPrincipal` against the security requirements of the field on the context and generates a `FieldAuthorizationResult` indicating if the user is authorized or not. This component makes no decisions in regards to the authorization state. It is up to the other pipelines to act on the authorization results that are generated.
167+
1. `FieldSecurityRequirementsMiddleware` : Gathers the authentication and authorization requirements for the given field and ensures that the field _can_ be authorized. There are some instances where by
168+
nested authorization requirements create a scenario in which no user could ever be authorized. This generally involves using multiple auth providers with specific authentication scheme requirements.
169+
2. `FieldAuthenticationMiddleware` : Authenticates the request to the field. This generates a ClaimsPrincipal to be authorized against.
170+
3. `FieldAuthorizationMiddleware`: Inspects the active `ClaimsPrincipal` against the security requirements of the field on the context and generates a `FieldAuthorizationResult` indicating if the user is authorized or not. This component makes no decisions in regards to the authorization state. It is up to the other pipelines to act on the authorization results that are generated.
169171

170172
#### GraphFieldAuthorizationContext
171173

172-
In addition to the common properties defined above the field authorization context defines a number of useful properties:
174+
In addition to the common properties defined above the field security context defines a number of useful properties:
173175

174176
```csharp
175-
public class GraphFieldAuthorizationContext
177+
public class GraphFieldSecurityContext
176178
{
177-
public IGraphFieldAuthorizationRequest Request { get; }
178-
public FieldAuthorizationResult Result { get; set; }
179+
public FieldSecurityRequirements SecurityRequirements {get; set;}
180+
public IGraphFieldSecurityRequest Request { get; }
181+
public FieldSecurityChallengeResult Result { get; set; }
179182

180183
// common properties omitted for brevity
181184
}
182185
```
183-
184-
- `Request`: Contains the field metadata for this context, including the security rules that need to be checked.
185-
- `Result`: The generated authorization result indicating if the user is authorized or unauthorized for the field. This result will contain additional detailed information as to why a request was not authorized. This information is automatically added to any generated log events.
186+
- `SecurityRequirements`: The security rules that need to be checked to authorize a user.
187+
- `Request`: Contains details about the field currently being authed.
188+
- `Result`: The generated challenge result indicating if the user is authorized or unauthorized for the field. This result will contain additional detailed information as to why a request was not authorized. This information is automatically added to any generated log events.
186189

187190

188191
## Directive Execution Pipeline
@@ -199,10 +202,15 @@ public class GraphDirectiveExecutionContext
199202
{
200203
public IGraphDirectiveRequest Request { get; }
201204
public IDirective Directive {get;}
205+
public ISchema Schema {get; }
202206

203207
// common properties omitted for brevity
204208
}
205209
```
206210

207211
- `Request`: Contains the directive metadata for this context, including the DirectiveTarget, execution phase and executing location.
208-
- `Directive`: The specific `IDirective`, registered to the schema, that is being processed.
212+
- `Directive`: The specific `IDirective`, registered to the schema, that is being processed.
213+
- `Schema`: the schema instance where the directive is declared.
214+
215+
> WARNING: Since the directive execution pipeline is used to construct the schema and apply type system directives, middleware components cannot inject a schema instance
216+
from the DI container. To do so will cause a circular reference. Instead use the schema instance attached to the `GraphDirectiveExecutionContext`. The state of this schema object is not guaranteed at during schema generation as it will continue to change as type system directives are applied by the pipeline.

docs/advanced/multiple-schema.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ title: Multi-Schema Support
44
sidebar_label: Multi-Schema Support
55
---
66

7-
GraphQL ASP.NET supports multiple schemas on the same server out of the box. Each schema is recognized by the runtime by its concrete type. To register multiple schemas you'll need to implement your own type that inherits from `ISchema`
7+
GraphQL ASP.NET supports multiple schemas on the same server out of the box. Each schema is recognized by the runtime by its concrete type. To register multiple schemas you'll need to create your own type that implements `ISchema`
88

99
## Implement ISchema
1010

11-
While it is possible to implement directly from `ISchema` if you don't require any extra functionality in your schema its easier to just subclass the default schema.
11+
While it is possible to implement `ISchema` directly, if you don't require any extra functionality in your schema its easier to just subclass the default schema.
1212

1313
```csharp
1414
public class EmployeeSchema : GraphSchema

0 commit comments

Comments
 (0)