Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
48 changes: 41 additions & 7 deletions docs/advanced/custom-scalars.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ public class GuidScalarSerializer : IScalarValueSerializer

---

The Completed Money Scalar:
### Example: Money Scalar
The completed Money custom scalar type

```csharp

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

services.AddMvc()
.AddGraphQL();
services.AddGraphQL();
}
```

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.
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.

## @specifiedBy Directive

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.

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.

```csharp
// apply the directive to a single schema
GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(MoneyScalarType));
services.AddGraphQL(o => {
o.ApplyDirective("@specifiedBy")
.WithArguments("https://myurl.com")
.ToItems(item => item.Name == "Money");
});

// via the ApplyDirective attribute
// for all schemas
[ApplyDirective("@specifiedBy", "https://myurl.com")]
public class MoneyScalarType : IScalarType
{
// ...
}

// via the special SpecifiedBy attribute
// for all schemas
[SpecifiedBy("https://myurl.com")]
public class MoneyScalarType : IScalarType
{
// ...
}

```

## Tips When Developing a Scalar

A few points about designing your scalar:

- 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.
- Scalar types are expected to be thread safe.
- 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.
- Scalar types should be simple and work in isolation.
- 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.
- 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.
- If you have a lot of logic to unpack a string, consider using a regular OBJECT graph type instead.
- Scalar types should not track any state or depend on any stateful objects.
- `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.
- `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.

### Aim for Fewer Scalars

Expand Down
51 changes: 46 additions & 5 deletions docs/advanced/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ This Directive:

### Example: @deprecated

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.

```csharp
public sealed class DeprecatedDirective : GraphDirective
{
Expand Down Expand Up @@ -435,13 +437,52 @@ type Person {

<br/>

### Repeatable Directives

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.

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
will be able to properly interprete your schema.

```csharp
// apply repeatable attribute
[Repeatable]
public sealed class ScanItemDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.OBJECT)]
public IGraphActionResult Execute(string scanType)
{ /* ... */}
}

// Option 1: Apply the directive to the class directly
[ApplyDirective("@scanItem", "medium")]
[ApplyDirective("@scanItem", "high")]
public class Person
{
}

// Option 2: Apply the directive at startup
services.AddGraphQL(o => {
// ...
o.ApplyDirective("@scanItem")
.WithArguments("medium")
.ToItems(item => item.IsObjectGraphType<Person>());
o.ApplyDirective("@scanItem")
.WithArguments("high")
.ToItems(item => item.IsObjectGraphType<Person>());
});
```

> Order matters. The repeated directives will be executed in the order they are encountered with those applied via attribution taking precedence.


## Directives as Services
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.
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.

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.

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

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

## Understanding the Type System
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.
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).

**UML Diagrams**

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,
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,
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.

**Helpful Extensions**

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.
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.

## Demo Project
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
Expand Down
32 changes: 20 additions & 12 deletions docs/advanced/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,8 @@ The field execution pipeline is executed once for each field of data that needs

1. `ValidateFieldExecutionMiddleware` : Validates that the context and required invocation data has been correctly supplied.
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.
3. `InvokeDirectiveResolversMiddleware` : Any directives that were attached to the active field are executed and their results acted on accordingly.
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.
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.
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.
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.

#### GraphFieldExecutionContext

Expand All @@ -165,24 +164,28 @@ public class GraphFieldExecutionContext

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:

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.
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
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.
2. `FieldAuthenticationMiddleware` : Authenticates the request to the field. This generates a ClaimsPrincipal to be authorized against.
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.

#### GraphFieldAuthorizationContext

In addition to the common properties defined above the field authorization context defines a number of useful properties:
In addition to the common properties defined above the field security context defines a number of useful properties:

```csharp
public class GraphFieldAuthorizationContext
public class GraphFieldSecurityContext
{
public IGraphFieldAuthorizationRequest Request { get; }
public FieldAuthorizationResult Result { get; set; }
public FieldSecurityRequirements SecurityRequirements {get; set;}
public IGraphFieldSecurityRequest Request { get; }
public FieldSecurityChallengeResult Result { get; set; }

// common properties omitted for brevity
}
```

- `Request`: Contains the field metadata for this context, including the security rules that need to be checked.
- `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.
- `SecurityRequirements`: The security rules that need to be checked to authorize a user.
- `Request`: Contains details about the field currently being authed.
- `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.


## Directive Execution Pipeline
Expand All @@ -199,10 +202,15 @@ public class GraphDirectiveExecutionContext
{
public IGraphDirectiveRequest Request { get; }
public IDirective Directive {get;}
public ISchema Schema {get; }

// common properties omitted for brevity
}
```

- `Request`: Contains the directive metadata for this context, including the DirectiveTarget, execution phase and executing location.
- `Directive`: The specific `IDirective`, registered to the schema, that is being processed.
- `Directive`: The specific `IDirective`, registered to the schema, that is being processed.
- `Schema`: the schema instance where the directive is declared.

> WARNING: Since the directive execution pipeline is used to construct the schema and apply type system directives, middleware components cannot inject a schema instance
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.
4 changes: 2 additions & 2 deletions docs/advanced/multiple-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ title: Multi-Schema Support
sidebar_label: Multi-Schema Support
---

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`
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`

## Implement ISchema

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.
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.

```csharp
public class EmployeeSchema : GraphSchema
Expand Down
Loading