-
-
Notifications
You must be signed in to change notification settings - Fork 749
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
89 changed files
with
22,209 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
716 changes: 716 additions & 0 deletions
716
website/src/docs/hotchocolate/v15/api-reference/apollo-federation.md
Large diffs are not rendered by default.
Oops, something went wrong.
192 changes: 192 additions & 0 deletions
192
website/src/docs/hotchocolate/v15/api-reference/custom-attributes.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
--- | ||
title: "Custom Attributes" | ||
--- | ||
|
||
Hot Chocolate allows to define a schema in various ways. When defining schemas with pure .NET types and custom attributes we need a way to access advanced features like custom field middleware that we have at our disposal with schema types. | ||
|
||
```csharp | ||
public class QueryType : ObjectType<Query> | ||
{ | ||
protected override void Configure(IObjectTypeDescriptor<Query> descriptor) | ||
{ | ||
descriptor.Field(t => t.Strings).UsePaging<StringType>(); | ||
} | ||
} | ||
``` | ||
|
||
This is where descriptor attributes come in. Descriptor attributes allow us to package descriptor configurations into an attribute that can be used to decorate our .NET types. Descriptor attributes act like an interceptor into the configuration of the inferred schema type. | ||
|
||
# Built-In Attributes | ||
|
||
We have prepared the following set of built-in descriptor attributes. | ||
|
||
> ⚠️ **Note:** As middleware comprises the stages of a sequential _pipeline_, the ordering is important. The correct order to use is `UsePaging`, `UseFiltering`, `UseSorting`. | ||
## UsePagingAttribute | ||
|
||
The `UsePagingAttribute` allows us to use the paging middleware by annotating it to a property or method. | ||
|
||
```csharp | ||
public class Query | ||
{ | ||
[UsePaging] | ||
public IQueryable<Foo> GetFoos() | ||
{ | ||
... | ||
} | ||
} | ||
``` | ||
|
||
## UseFilteringAttribute | ||
|
||
The `UseFilteringAttribute` allows us to apply the filtering middleware to a property or method. | ||
|
||
```csharp | ||
public class Query | ||
{ | ||
[UseFiltering] | ||
public IQueryable<Foo> GetFoos() | ||
{ | ||
... | ||
} | ||
} | ||
``` | ||
|
||
> Warning: Be sure to install the `HotChocolate.Types.Filters` NuGet package. | ||
## UseSortingAttribute | ||
|
||
The `UseSortingAttribute` allows us to apply the sorting middleware to a property or method. | ||
|
||
```csharp | ||
public class Query | ||
{ | ||
[UseSorting] | ||
public IQueryable<Foo> GetFoos() | ||
{ | ||
... | ||
} | ||
} | ||
``` | ||
|
||
> Warning: Be sure to install the `HotChocolate.Types.Sorting` NuGet package. | ||
## AuthorizeAttribute | ||
|
||
The `AuthorizeAttribute` allows to apply the authorize directives to a class, struct, interface, property or method. The attribute will only be applied if the inferred type is an object type. | ||
|
||
```csharp | ||
public class Query | ||
{ | ||
[Authorize(Policy = "MyPolicy")] | ||
public IQueryable<Foo> GetFoos() | ||
{ | ||
... | ||
} | ||
} | ||
``` | ||
|
||
# Attribute Chaining | ||
|
||
Attributes can by default be chained, meaning that the attributes are applied in order from the top one to the bottom one. | ||
|
||
The following code ... | ||
|
||
```csharp | ||
public class Query | ||
{ | ||
[UsePaging] | ||
[UseFiltering] | ||
[UseSorting] | ||
public IQueryable<Foo> GetFoos() | ||
{ | ||
... | ||
} | ||
} | ||
``` | ||
|
||
... would translate to: | ||
|
||
```csharp | ||
public class QueryType | ||
: ObjectType<Query> | ||
{ | ||
protected override void Configure(IObjectTypeDescriptor<Query> descriptor) | ||
{ | ||
descriptor.Field(t => t.Foos) | ||
.UsePaging<ObjectType<Foo>>() | ||
.UseFiltering() | ||
.UseSorting(); | ||
} | ||
} | ||
``` | ||
|
||
# Custom Descriptor Attributes | ||
|
||
It is super simple to create custom descriptor attributes and package complex functionality in simple to use attributes. | ||
|
||
```csharp | ||
public class SomeMiddlewareAttribute | ||
: ObjectFieldDescriptorAttribute | ||
{ | ||
public override void OnConfigure( | ||
IDescriptorContext context, | ||
IObjectFieldDescriptor descriptor, | ||
MemberInfo member) | ||
{ | ||
descriptor.Use(next => context => ...); | ||
} | ||
} | ||
``` | ||
|
||
Within the `OnConfigure` method you can do what you actually would do in the `Configure` method of a type. | ||
|
||
But you also get some context information about where the configuration was applied to, like you get the member to which the attribute was applied to and you get the descriptor context. | ||
|
||
We have one descriptor base class for each first-class descriptor type. | ||
|
||
- EnumTypeDescriptorAttribute | ||
- EnumValueDescriptorAttribute | ||
- InputObjectTypeDescriptorAttribute | ||
- InputFieldDescriptorAttribute | ||
- InterfaceTypeDescriptorAttribute | ||
- InterfaceFieldDescriptorAttribute | ||
- ObjectTypeDescriptorAttribute | ||
- ObjectFieldDescriptorAttribute | ||
- UnionTypeDescriptorAttribute | ||
- ArgumentDescriptorAttribute | ||
|
||
All of these attribute base classes have already the allowed attribute targets applied. That means that we pre-configured the `ObjectFieldDescriptorAttribute` for instance to be only valid on methods and properties. | ||
|
||
If you want to build more complex attributes that can be applied to multiple targets like an interface type and an object type at the same time then you can use our `DescriptorAttribute` base class. This base class is not pre-configured and lets you probe for configuration types. | ||
|
||
```csharp | ||
[AttributeUsage( | ||
AttributeTargets.Property | AttributeTargets.Method, | ||
Inherited = true, | ||
AllowMultiple = true)] | ||
public sealed class MyCustomAttribute : DescriptorAttribute | ||
{ | ||
protected override void TryConfigure( | ||
IDescriptorContext context, | ||
IDescriptor descriptor, | ||
ICustomAttributeProvider element) | ||
{ | ||
if(element is MemberInfo member) | ||
{ | ||
switch(descriptor) | ||
{ | ||
case IInterfaceFieldDescriptor interfaceField: | ||
// do something ... | ||
break; | ||
|
||
case IObjectFieldDescriptor interfaceField: | ||
// do something ... | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
It is simple to use these attributes. Just annotating a type or a property with an attribute will add the packaged functionality. The types can be used in conjunction with schema types or without. |
93 changes: 93 additions & 0 deletions
93
website/src/docs/hotchocolate/v15/api-reference/custom-context-data.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
--- | ||
title: Custom Context Data | ||
--- | ||
|
||
When implementing custom middleware, it can be useful to be able to store some custom state on the context. This could be to build up a cache or other state data. Hot Chocolate has two types of context stores that we can use. | ||
|
||
# Global Context Data | ||
|
||
The global context data is a thread-safe dictionary that is available though the `IQueryContext` and the `IResolverContext`. This means we are able to share context data between query middleware components and field middleware components. | ||
|
||
One common use case is to aggregate some state when the GraphQL request is created and use it in field middleware or in the resolver. | ||
|
||
In order to intercept the request creation we can add an `IOperationRequestInterceptor` to our services and there build up our custom state. | ||
|
||
```csharp | ||
services.AddQueryRequestInterceptor((ctx, builder, ct) => | ||
{ | ||
builder.SetProperty("Foo", new Foo()); | ||
return Task.CompletedTask; | ||
}); | ||
``` | ||
|
||
We can access the initial provided data in a query middleware, field middleware or our resolver. | ||
|
||
Query Middleware Example: | ||
|
||
```csharp | ||
builder.Use(next => context => | ||
{ | ||
// access data | ||
var foo = (Foo)context.ContextData["Foo"]; | ||
|
||
// set new data | ||
context.ContextData["Bar"] = new Bar(); | ||
|
||
return next.Invoke(context); | ||
}); | ||
``` | ||
|
||
Field Middleware Example: | ||
|
||
```csharp | ||
SchemaBuilder.New() | ||
.Use(next => context => | ||
{ | ||
// access data | ||
var foo = (Foo)context.ContextData["Foo"]; | ||
|
||
// set new data | ||
context.ContextData["Bar"] = new Bar(); | ||
|
||
return next.Invoke(context); | ||
}) | ||
.Create(); | ||
``` | ||
|
||
Resolver Example: | ||
|
||
```csharp | ||
public Task<string> MyResolver([State("Foo")]Foo foo) | ||
{ | ||
... | ||
} | ||
``` | ||
|
||
# Scoped Context Data | ||
|
||
The scoped context data is a immutable dictionary and is only available through the `IResolverContext`. | ||
|
||
Scoped state allows us to aggregate state for our child field resolvers. | ||
|
||
Let's say we have the following query: | ||
|
||
```graphql | ||
{ | ||
a { | ||
b { | ||
c | ||
} | ||
} | ||
d { | ||
e { | ||
f | ||
} | ||
} | ||
} | ||
``` | ||
|
||
If the `a`-resolver would put something on the scoped context its sub-tree could access that data. This means, `b` and `c` could access the data but `d`, `e` and `f` would _NOT_ be able to access the data, their dictionary is still unchanged. | ||
|
||
```csharp | ||
context.ScopedContextData = context.ScopedContextData.SetItem("foo", "bar"); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
--- | ||
title: Errors | ||
--- | ||
|
||
GraphQL errors in Hot Chocolate are passed to the operation result by returning an instance of `IError` or an enumerable of `IError` in a field resolver. | ||
|
||
Moreover, you can throw a `GraphQLException` that will be be caught by the execution engine and translated to a field error. | ||
|
||
One further way to raise an error are non-terminating field errors. This can be raised by using `IResolverContext.ReportError`. With this you can provide a result and raise an error for your current field. | ||
|
||
> If you do want to log errors head over to our diagnostic source [documentation](/docs/hotchocolate/v15/server/instrumentation) and see how you can hook up your logging framework of choice to it. | ||
# Error Builder | ||
|
||
Since errors can have a lot of properties, we have introduced a new error builder which provides a nice API without thousands of overloads. | ||
|
||
```csharp | ||
var error = ErrorBuilder | ||
.New() | ||
.SetMessage("This is my error.") | ||
.SetCode("FOO_BAR") | ||
.Build(); | ||
``` | ||
|
||
# Error Filters | ||
|
||
If some other exception is thrown during execution, then the execution engine will create an instance of `IError` with the message **Unexpected Execution Error** and the actual exception assigned to the error. However, the exception details will not be serialized so by default the user will only see the error message **Unexpected Execution Error**. | ||
|
||
If you want to translate exceptions into errors with useful information then you can write an `IErrorFilter`. | ||
|
||
An error filter has to be registered as a service. | ||
|
||
```csharp | ||
builder.Services.AddErrorFilter<MyErrorFilter>(); | ||
``` | ||
|
||
It is also possible to just register the error filter as a delegate like the following. | ||
|
||
```csharp | ||
builder.Services.AddErrorFilter(error => | ||
{ | ||
if (error.Exception is NullReferenceException) | ||
{ | ||
return error.WithCode("NullRef"); | ||
} | ||
|
||
return error; | ||
}); | ||
``` | ||
|
||
Since errors are immutable we have added some helper functions like `WithMessage`, `WithCode` and so on that create a new error with the desired properties. Moreover, you can create an error builder from an error and modify multiple properties and then rebuild the error object. | ||
|
||
```csharp | ||
return ErrorBuilder | ||
.FromError(error) | ||
.SetMessage("This is my error.") | ||
.SetCode("FOO_BAR") | ||
.Build(); | ||
``` | ||
|
||
# Exception Details | ||
|
||
In order to automatically add exception details to your GraphQL errors, you can enable the `IncludeExceptionDetails` option. By default this will be enabled when the debugger is attached. | ||
|
||
```csharp | ||
builder | ||
.AddGraphQL() | ||
.ModifyRequestOptions( | ||
o => o.IncludeExceptionDetails = | ||
builder.Environment.IsDevelopment()); | ||
``` |
Oops, something went wrong.