Skip to content

Commit 22755ec

Browse files
tobias-tenglerbenmccallum
andauthoredJul 14, 2021
Expand resolver documentation (#3802)
Co-authored-by: Ben McCallum <ben.mccallum@live.com.au>
1 parent e45e9d7 commit 22755ec

File tree

4 files changed

+614
-273
lines changed

4 files changed

+614
-273
lines changed
 

‎website/src/docs/docs.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@
9696
"items": [
9797
{
9898
"path": "index",
99-
"title": "Resolver"
99+
"title": "Overview"
100+
},
101+
{
102+
"path": "resolvers",
103+
"title": "Resolvers"
100104
},
101105
{
102106
"path": "fetching-from-databases",

‎website/src/docs/hotchocolate/defining-a-schema/object-types.md

+22-8
Original file line numberDiff line numberDiff line change
@@ -196,14 +196,20 @@ public class BookType : ObjectType<Book>
196196

197197
# Naming
198198

199-
Hot Chocolate infers the names of the object types and their fields automatically, but sometimes we might want to specify names ourselves.
199+
Unless specified explicitly, Hot Chocolate automatically infers the names of object types and their fields. Per default the name of the class becomes the name of the object type. When using `ObjectType<T>` in Code-first, the name of `T` is chosen as the name for the object type. The names of methods and properties on the respective class are chosen as names of the fields of the object type.
200+
201+
The following conventions are applied when transforming C# method and property names into SDL types and fields:
202+
203+
- **Get prefixes are removed:** The get operation is implied and therefore redundant information.
204+
- **Async postfixes are removed:** The `Async` is an implementation detail and therefore not relevant to the schema.
205+
- **The first letter is lowercased:** This is not part of the specification, but a widely agreed upon standard in the GraphQL world.
206+
207+
If we need to we can override these inferred names.
200208

201209
<ExampleTabs>
202210
<ExampleTabs.Annotation>
203211

204-
Per default the name of the class is the name of the object type in the schema and the names of the properties are the names of the fields of that object type.
205-
206-
We can override these defaults using the `[GraphQLName]` attribute.
212+
The `[GraphQLName]` attribute allows us to specify an explicit name.
207213

208214
```csharp
209215
[GraphQLName("BookAuthor")]
@@ -217,9 +223,7 @@ public class Author
217223
</ExampleTabs.Annotation>
218224
<ExampleTabs.Code>
219225

220-
By default, the name of the object type in the schema is either the class name, if we are inheriting from `ObjectType`, or the name of the POCO (`T`) if we are inheriting from `ObjectType<T>`.
221-
222-
We can override these defaults using the `Name` method on the `IObjectTypeDescriptor` / `IObjectFieldDescriptor`.
226+
The `Name` method on the `IObjectTypeDescriptor` / `IObjectFieldDescriptor` allows us to specify an explicit name.
223227

224228
```csharp
225229
public class AuthorType : ObjectType<Author>
@@ -251,6 +255,16 @@ type BookAuthor {
251255
}
252256
```
253257

258+
If only one of our clients requires specific names, it is better to use [aliases](https://graphql.org/learn/queries/#aliases) in this client's operations than changing the entire schema.
259+
260+
```graphql
261+
{
262+
MyUser: user {
263+
Username: name
264+
}
265+
}
266+
```
267+
254268
# Explicit types
255269

256270
Hot Chocolate will, most of the time, correctly infer the schema types of our fields. Sometimes we might have to be explicit about it though, for example when we are working with custom scalars.
@@ -367,7 +381,7 @@ public class Startup
367381

368382
What we have just created is a resolver. Hot Chocolate automatically creates resolvers for our properties, but we can also define them ourselves.
369383

370-
Head over to the [resolver documentation](/docs/hotchocolate/fetching-data) to learn more.
384+
[Learn more about resolvers](/docs/hotchocolate/fetching-data/resolvers)
371385

372386
# Generics
373387

Original file line numberDiff line numberDiff line change
@@ -1,289 +1,49 @@
11
---
2-
title: "Resolver"
2+
title: Overview
33
---
44

5-
import { ExampleTabs } from "../../../components/mdx/example-tabs"
5+
In this section we will learn everything about fetching data with Hot Chocolate.
66

7-
Here we will learn what resolvers are, how they are defined, and what else we could do with them in Hot Chocolate.
7+
# Resolvers
88

9-
# Introduction
9+
Resolvers are the main building blocks when it comes to fetching data. Every field in our GraphQL schema is backed by such a resolver function, responsible for returning the field's value. Since a resolver is just a function, we can use it to retrieve data from a database, a REST service, or any other data source as needed.
1010

11-
When it comes to fetching data in a GraphQL server, you will always end up with a resolver.
12-
**A resolver is a generic function that fetches data from an arbitrary data source for a particular field.**
13-
It means every field has its specific resolver function to fetch or select data. Even if there isn't a resolver defined for one field, Hot Chocolate will create a default resolver for this particular field behind the scenes.
11+
[Learn more about resolvers](/docs/hotchocolate/fetching-data/resolvers)
1412

15-
```mermaid
16-
graph TD
17-
A(field) --> B{has resolver?}
18-
B --> |yes|C(return resolver)
19-
B --> |no|D(create default resolver)
20-
D --> C
21-
```
13+
Even though we can connect Hot Chocolate to any data source, most of the time it will be either a database or a REST service.
2214

23-
In Hot Chocolate, a default resolver is a compiled function for a specific field that accesses a property of its parent value, which matches with the field name. For example, if we have a parent value of type `User`, which has a field called `name`, the compiled default resolver for the field `name` would look like the following.
15+
[Learn how to fetch data from a database](/docs/hotchocolate/fetching-data/fetching-from-databases)
2416

25-
```csharp
26-
var resolver = (User parent) => parent.Name;
27-
```
17+
[Learn how to fetch data from a REST service](/docs/hotchocolate/fetching-data/fetching-from-rest)
2818

29-
It's not exactly how it's implemented in Hot Chocolate, but it serves here basically as a simplified illustration. The key takeaway is that there is always a resolver for every field in place.
19+
# DataLoader
3020

31-
> **Note:** The parent value represents the parent resolver's inner value, or in the case of a root resolver, the root value, which means the root type's value (query, mutation, or subscription). It has nothing to do with the result type of a resolver and is specific to the business logic.
21+
DataLoaders provide a way to deduplicate and batch requests to data sources. They can significantly improve the performance of our queries and ease the load on our data sources.
3222

33-
## Resolver Tree
23+
[Learn more about DataLoaders](/docs/hotchocolate/fetching-data/dataloader)
3424

35-
A resolver tree is a projection of a GraphQL operation that is prepared for execution. The execution engine takes the resolver tree and follows the path of resolvers from top to down. For better understanding, let's imagine we have a simple GraphQL query like the following, where we select the currently logged-in user's name.
25+
# Pagination
3626

37-
```graphql
38-
query {
39-
me {
40-
name
41-
}
42-
}
43-
```
27+
Hot Chocolate provides pagination capabilities out of the box. They allow us to expose pagination in a standardized way and can even take care of crafting the necessary pagination queries to our databases.
4428

45-
In Hot Chocolate, this query results in the following resolver tree.
29+
[Learn more about pagination](/docs/hotchocolate/fetching-data/pagination)
4630

47-
```mermaid
48-
graph TD
49-
A(query: QueryType) --> B("me() => [UserType]")
50-
B --> C("name() => StringType")
51-
```
31+
# Filtering
5232

53-
A resolver tree is, in the end, nothing else than a resolver chain where each branch can be executed in parallel.
33+
When returning a list of entites, we often need to filter them using operations like `equals`, `contains`, `startsWith`, etc. Hot Chocolate takes away a lot of the boilerplate, by handling the generation of necessary input types and even translating the applied filters into native database queries.
5434

55-
```mermaid
56-
graph LR
57-
A("me()") --> B("name()")
58-
```
35+
[Learn more about filtering](/docs/hotchocolate/fetching-data/filtering)
5936

60-
Okay, let's dissect a little further here. A resolver chain always starts with one or many root resolver, which is in our case `me()` and then follows the path along. In this scenario, the next resolver would be `name()`, which is also the last resolver in our chain. As soon as `me` has fetched the user profile of the currently logged-in user, Hot Chocolate will immediately start executing the next resolver and feeding in the previous object value, also called a parent or parent value in spec language. Let's say the parent value looks like this.
37+
# Sorting
6138

62-
```csharp
63-
var parent = new User
64-
{
65-
Id = "user-1",
66-
Name = "ChilliCream",
67-
...
68-
}
69-
```
39+
Similar to filtering, Hot Chocolate can also autogenerate input types related to sorting. They allow us to specify by which fields and in which direction our entities should be sorted. These can also be translated into native database queries automatically.
7040

71-
Then the `name()` resolver can just access the `Name` property of the parent value and simply return it. As soon as all resolvers have been completed, the execution engine would return the following GraphQL result, provided that everything went successful.
41+
[Learn more about sorting](/docs/hotchocolate/fetching-data/sorting)
7242

73-
```json
74-
{
75-
"data": {
76-
"me": {
77-
"name": "ChilliCream"
78-
}
79-
}
80-
}
81-
```
43+
# Projections
8244

83-
Excellent, now that we know what resolvers are and how they work in a bigger picture, how can we start writing one. Let's jump to the next section and find out.
45+
Projections allow Hot Chocolate to transform an incoming GraphQL query with a subselection of fields into an optimized database operation.
8446

85-
# Defining a resolver
47+
For example, if the client only requests the `name` and `id` of a user in their GraphQL query, Hot Chocolate will only query the database for those two columns.
8648

87-
A resolver is a function that takes zero or many arguments and returns one value. The simplest resolver to write is a resolver that takes zero arguments and returns a simple value type (e.g., a string). For simplicity, we will do precisely that in our first example. Creating a resolver named `Say` with no arguments, which returns just a static string value `Hello World!`.
88-
89-
## Basic resolver example
90-
91-
<ExampleTabs>
92-
<ExampleTabs.Annotation>
93-
94-
```csharp
95-
// Query.cs
96-
public class Query
97-
{
98-
public string Say() => "Hello World!";
99-
}
100-
101-
// Startup.cs
102-
public class Startup
103-
{
104-
public void ConfigureServices(IServiceCollection services)
105-
{
106-
services
107-
.AddGraphQLServer()
108-
.AddQueryType<Query>();
109-
}
110-
111-
// Omitted code for brevity
112-
}
113-
```
114-
115-
</ExampleTabs.Annotation>
116-
<ExampleTabs.Code>
117-
118-
```csharp
119-
// Query.cs
120-
public class Query
121-
{
122-
public string Say() => "Hello World!";
123-
}
124-
125-
// QueryType.cs
126-
public class QueryType
127-
: ObjectType<Query>
128-
{
129-
protected override void Configure(
130-
IObjectTypeDescriptor<Query> descriptor)
131-
{
132-
descriptor
133-
.Field(f => f.Say())
134-
.Type<NonNullType<StringType>>();
135-
}
136-
}
137-
138-
// Startup.cs
139-
public class Startup
140-
{
141-
public void ConfigureServices(IServiceCollection services)
142-
{
143-
services
144-
.AddGraphQLServer()
145-
.AddQueryType<QueryType>();
146-
}
147-
148-
// Omitted code for brevity
149-
}
150-
```
151-
152-
</ExampleTabs.Code>
153-
<ExampleTabs.Schema>
154-
155-
```csharp
156-
// Query.cs
157-
public class Query
158-
{
159-
public string Say() => "Hello World!";
160-
}
161-
162-
// Startup.cs
163-
public class Startup
164-
{
165-
public void ConfigureServices(IServiceCollection services)
166-
{
167-
services
168-
.AddGraphQLServer()
169-
.AddDocumentFromString(@"
170-
type Query {
171-
say: String!
172-
}
173-
")
174-
.BindComplexType<Query>();
175-
}
176-
177-
// Omitted code for brevity
178-
}
179-
```
180-
181-
</ExampleTabs.Schema>
182-
</ExampleTabs>
183-
184-
When comparing all three approaches side-by-side, we can see very quickly that they all look nearly the same. They all have the `Query` type in common, which is identical in all three approaches. Regardless, the `Query` type contains a method named `Say`, which is our resolver, in fact, the most significant bit here. The `Say` method will be translated into the `say` field on the schema side as soon as Hot Chocolate is initialized. As a small side note here, all three approaches will result in the same `SDL`.
185-
186-
```sdl
187-
type Query {
188-
say: String!
189-
}
190-
```
191-
192-
Let's get back to where the approaches differentiate—the `Startup` class, which contains the service configuration that slightly differs in each approach. In the **annotation-based** approach, we bind the `Query` type to the GraphQL schema. Easy, quick, and without writing any GraphQL specific binding code. Hot Chocolate will do the hard part and infer everything from the type itself. In the **code-first** approach, we bind a meta-type, the `QueryType` type, which contains the GraphQL configuration for the `Query` type, to the GraphQL schema. Instead of inferring the GraphQL type, Hot Chocolate will take our specific GraphQL configuration and creates the GraphQL schema out of it. In the **schema-first** approach, we provide Hot Chocolate the `SDL` directly, and Hot Chocolate will match that to our resolver. Now that we know how to define a resolver in all three approaches, it's time to learn how to pass arguments into a resolver. Let's head to the next section.
193-
194-
# Resolver Arguments
195-
196-
A resolver argument, not to be confused with a field argument in GraphQL, can be a field argument value, a DataLoader, a DI service, state, or even context like a parent value. We will go through a couple of examples where we see all types of resolver argument in action. For that, we will use the annotation-based approach because it makes no difference.
197-
198-
## Field argument example
199-
200-
```csharp
201-
// Query.cs
202-
public class Query
203-
{
204-
public string Say(string name) => $"Hello {name}!";
205-
}
206-
207-
// Startup.cs
208-
public class Startup
209-
{
210-
public void ConfigureServices(IServiceCollection services)
211-
{
212-
services
213-
.AddGraphQLServer()
214-
.AddQueryType<Query>();
215-
}
216-
217-
// Omitted code for brevity
218-
}
219-
```
220-
221-
```sdl
222-
type Query {
223-
say(name: String!): String!
224-
}
225-
```
226-
227-
## DataLoader argument example
228-
229-
```csharp
230-
// Query.cs
231-
public class Query
232-
{
233-
public Task<Person> GetPerson(int id, MyDataLoader dataLoader) => dataLoader.LoadAsync(id);
234-
}
235-
236-
// Person.cs
237-
public record Person(string name);
238-
239-
// MyDataLoader.cs
240-
public class MyDataLoader
241-
: DataLoaderBase<int, Person>
242-
{
243-
public MyDataLoader(IBatchScheduler scheduler)
244-
: base(scheduler)
245-
{ }
246-
247-
protected override ValueTask<IReadOnlyList<Result<Person>>> FetchAsync(
248-
IReadOnlyList<int> keys,
249-
CancellationToken cancellationToken)
250-
{
251-
// Omitted code for brevity
252-
}
253-
}
254-
255-
// Startup.cs
256-
public class Startup
257-
{
258-
public void ConfigureServices(IServiceCollection services)
259-
{
260-
services
261-
.AddGraphQLServer()
262-
.AddQueryType<Query>()
263-
.AddDataLoader<MyDataLoader>();
264-
}
265-
266-
// Omitted code for brevity
267-
}
268-
```
269-
270-
```sdl
271-
type Query {
272-
person(id: Int!): Person!
273-
}
274-
275-
type Person {
276-
name: String!
277-
}
278-
```
279-
280-
# Naming Rules
281-
282-
- How should we name things
283-
- How is a method name translated
284-
285-
# Best Practices
286-
287-
# Resolver Pipeline
288-
289-
# Error Handling
49+
[Learn more about projections](/docs/hotchocolate/fetching-data/projections)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,563 @@
1+
---
2+
title: "Resolvers"
3+
---
4+
5+
import { ExampleTabs } from "../../../components/mdx/example-tabs"
6+
7+
When it comes to fetching data in a GraphQL server, it will always come down to a resolver.
8+
9+
**A resolver is a generic function that fetches data from an arbitrary data source for a particular field.**
10+
11+
We can think of each field in our query as a method of the previous type which returns the next type.
12+
13+
## Resolver Tree
14+
15+
A resolver tree is a projection of a GraphQL operation that is prepared for execution.
16+
17+
For better understanding, let's imagine we have a simple GraphQL query like the following, where we select some fields of the currently logged-in user.
18+
19+
```graphql
20+
query {
21+
me {
22+
name
23+
company {
24+
id
25+
name
26+
}
27+
}
28+
}
29+
```
30+
31+
In Hot Chocolate, this query results in the following resolver tree.
32+
33+
```mermaid
34+
graph LR
35+
A(query: QueryType) --> B(me: UserType)
36+
B --> C(name: StringType)
37+
B --> D(company: CompanyType)
38+
D --> E(id: IdType)
39+
D --> F(name: StringType)
40+
```
41+
42+
This tree will be traversed by the execution engine, starting with one or more root resolvers. In the above example the `me` field represents the only root resolver.
43+
44+
Field resolvers that are subselections of a field, can only be executed after a value has been resolved for their _parent_ field. In the case of the above example this means that the `name` and `company` resolvers can only run, after the `me` resolver has finished. Resolvers of field subselections can and will be executed in parallel.
45+
46+
**Because of this it is important that resolvers, with the exception of top level mutation field resolvers, do not contain side-effects, since their execution order may vary.**
47+
48+
The execution of a request finishes, once each resolver of the selected fields has produced a result.
49+
50+
_This is of course an oversimplification that differs from the actual implementation._
51+
52+
# Defining a Resolver
53+
54+
Resolvers can be defined in a way that should feel very familiar to C# developers, especially in the Annotation-based approach.
55+
56+
## Properties
57+
58+
Hot Chocolate automatically converts properties with a public get accessor to a resolver that simply returns its value.
59+
60+
Properties are also covered in detail by the [object type documentation](/docs/hotchocolate/defining-a-schema/object-types).
61+
62+
## Regular Resolver
63+
64+
A regular resolver is just a simple method, which returns a value.
65+
66+
<ExampleTabs>
67+
<ExampleTabs.Annotation>
68+
69+
```csharp
70+
public class Query
71+
{
72+
public string Foo() => "Bar";
73+
}
74+
75+
public class Startup
76+
{
77+
public void ConfigureServices(IServiceCollection services)
78+
{
79+
services
80+
.AddGraphQLServer()
81+
.AddQueryType<Query>();
82+
}
83+
}
84+
```
85+
86+
</ExampleTabs.Annotation>
87+
<ExampleTabs.Code>
88+
89+
```csharp
90+
public class Query
91+
{
92+
public string Foo() => "Bar";
93+
}
94+
95+
public class QueryType: ObjectType<Query>
96+
{
97+
protected override void Configure(IObjectTypeDescriptor<Query> descriptor)
98+
{
99+
descriptor
100+
.Field(f => f.Foo())
101+
.Type<NonNullType<StringType>>();
102+
}
103+
}
104+
105+
public class Startup
106+
{
107+
public void ConfigureServices(IServiceCollection services)
108+
{
109+
services
110+
.AddGraphQLServer()
111+
.AddQueryType<QueryType>();
112+
}
113+
}
114+
```
115+
116+
We can also provide a resolver delegate by using the `Resolve` method.
117+
118+
```csharp
119+
descriptor
120+
.Field("foo")
121+
.Resolve(context =>
122+
{
123+
return "Bar";
124+
});
125+
```
126+
127+
</ExampleTabs.Code>
128+
<ExampleTabs.Schema>
129+
130+
```csharp
131+
public class Query
132+
{
133+
public string Foo() => "Bar";
134+
}
135+
136+
public class Startup
137+
{
138+
public void ConfigureServices(IServiceCollection services)
139+
{
140+
services
141+
.AddGraphQLServer()
142+
.AddDocumentFromString(@"
143+
type Query {
144+
foo: String!
145+
}
146+
")
147+
.BindComplexType<Query>();
148+
}
149+
}
150+
```
151+
152+
We can also add a resolver by calling `AddResolver()` on the `IRequestExecutorBuilder`.
153+
154+
```csharp
155+
services
156+
.AddGraphQLServer()
157+
.AddDocumentFromString(@"
158+
type Query {
159+
foo: String!
160+
}
161+
")
162+
.AddResolver("Query", "foo", (context) => "Bar");
163+
```
164+
165+
</ExampleTabs.Schema>
166+
</ExampleTabs>
167+
168+
## Async Resolver
169+
170+
Most data fetching operations, like calling a service or communicating with a database, will be asynchronous.
171+
172+
In Hot Chocolate, we can simply mark our resolver methods and delegates as `async` or return a `Task<T>` and it becomes an async-capable resolver.
173+
174+
We can also add a `CancellationToken` argument to our resolver. Hot Chocolate will automatically cancel this token if the request has been aborted.
175+
176+
```csharp
177+
public class Query
178+
{
179+
public async Task<string> Foo(CancellationToken ct)
180+
{
181+
// Omitted code for brevity
182+
}
183+
}
184+
```
185+
186+
When using a delegate resolver, the `CancellationToken` is passed as second argument to the delegate.
187+
188+
```csharp
189+
descriptor
190+
.Field("foo")
191+
.Resolve((context, ct) =>
192+
{
193+
// Omitted code for brevity
194+
});
195+
```
196+
197+
The `CancellationToken` can also be accessed through the `IResolverContext`.
198+
199+
```csharp
200+
descriptor
201+
.Field("foo")
202+
.Resolve(context =>
203+
{
204+
CancellationToken ct = context.RequestAborted;
205+
206+
// Omitted code for brevity
207+
});
208+
```
209+
210+
## ResolveWith
211+
212+
Thus far we have looked at two ways to specify resolvers in Code-first:
213+
214+
- Add new methods to the CLR type, e.g. the `T` type of `ObjectType<T>`
215+
- Add new fields to the schema type in the form of delegates
216+
```csharp
217+
descriptor.Field("foo").Resolve(context => )
218+
```
219+
220+
But there's a third way. We can describe our field using the `descriptor`, but instead of a resolver delegate, we can point to a method on another class, responsible for resolving this field.
221+
222+
```csharp
223+
public class FooResolvers
224+
{
225+
public string GetFoo(string arg, [Service] FooService service)
226+
{
227+
// Omitted code for brevity
228+
}
229+
}
230+
231+
public class QueryType : ObjectType
232+
{
233+
protected override void Configure(IObjectTypeDescriptor descriptor)
234+
{
235+
descriptor
236+
.Field("foo")
237+
.Argument("arg", a => a.Type<NonNullType<StringType>>())
238+
.ResolveWith<FooResolvers>(r => r.GetFoo(default, default));
239+
}
240+
}
241+
```
242+
243+
# Arguments
244+
245+
We can access arguments we defined for our resolver like regular arguments of a function.
246+
247+
There are also specific arguments that will be automatically populated by Hot Chocolate when the resolver is executed. These include [Dependency injection services](#injecting-services), [DataLoaders](/docs/hotchocolate/fetching-data/dataloader), state, or even context like a [_parent_](#accessing-parent-values) value.
248+
249+
[Learn more about arguments](/docs/hotchocolate/defining-a-schema/arguments)
250+
251+
# Injecting Services
252+
253+
Resolvers integrate nicely with `Microsoft.Extensions.DependecyInjection`.
254+
We can access all registered services in our resolvers.
255+
256+
Let's assume we have created a `UserService` and registered it as a service.
257+
258+
```csharp
259+
public class Startup
260+
{
261+
public void ConfigureServices(IServiceCollection services)
262+
{
263+
services.AddSingleton<UserService>()
264+
265+
services
266+
.AddGraphQLServer()
267+
.AddQueryType<Query>();
268+
}
269+
}
270+
```
271+
272+
We can then access the `UserService` in our resolvers like the following.
273+
274+
<ExampleTabs>
275+
<ExampleTabs.Annotation>
276+
277+
```csharp
278+
public class Query
279+
{
280+
public List<User> GetUsers([Service] UserService userService)
281+
=> userService.GetUsers();
282+
}
283+
```
284+
285+
</ExampleTabs.Annotation>
286+
<ExampleTabs.Code>
287+
288+
```csharp
289+
public class Query
290+
{
291+
public List<User> GetUsers([Service] UserService userService)
292+
=> userService.GetUsers();
293+
}
294+
295+
public class QueryType: ObjectType<Query>
296+
{
297+
protected override void Configure(IObjectTypeDescriptor<Query> descriptor)
298+
{
299+
descriptor
300+
.Field(f => f.Foo(default))
301+
.Type<ListType<UserType>>();
302+
}
303+
}
304+
```
305+
306+
When using the `Resolve` method, we can access services through the `IResolverContext`.
307+
308+
```csharp
309+
descriptor
310+
.Field("foo")
311+
.Resolve(context =>
312+
{
313+
var userService = context.Service<UserService>();
314+
315+
return userService.GetUsers();
316+
});
317+
```
318+
319+
</ExampleTabs.Code>
320+
<ExampleTabs.Schema>
321+
322+
```csharp
323+
public class Query
324+
{
325+
public List<User> GetUsers([Service] UserService userService)
326+
=> userService.GetUsers();
327+
}
328+
```
329+
330+
When using `AddResolver()`, we can access services through the `IResolverContext`.
331+
332+
```csharp
333+
services
334+
.AddGraphQLServer()
335+
.AddDocumentFromString(@"
336+
type Query {
337+
users: [User!]!
338+
}
339+
")
340+
.AddResolver("Query", "users", (context) =>
341+
{
342+
var userService = context.Service<UserService>();
343+
344+
return userService.GetUsers();
345+
});
346+
```
347+
348+
</ExampleTabs.Schema>
349+
</ExampleTabs>
350+
351+
Hot Chocolate will correctly inject the service depending on its lifetime. For example, a scoped service is only instantiated once per scope (by default that's the GraphQL request execution) and this same instance is injected into all resolvers who share the same scope.
352+
353+
## Constructor Injection
354+
355+
Of course we can also inject services into the constructor of our types.
356+
357+
```csharp
358+
public class Query
359+
{
360+
private readonly UserService _userService;
361+
362+
public Query(UserService userService)
363+
{
364+
_userService = userService;
365+
}
366+
367+
public List<User> GetUsers()
368+
=> _userService.GetUsers();
369+
}
370+
```
371+
372+
It's important to note that the service lifetime of types is singleton per default for performance reasons.
373+
374+
**This means one instance per injected service is kept around and used for the entire lifetime of the GraphQL server, regardless of the original lifetime of the service.**
375+
376+
If we depend on truly transient or scoped services, we need to inject them directly into the dependent methods as described [above](#injecting-services).
377+
378+
[Learn more about service lifetimes in ASP.NET Core](https://docs.microsoft.com/dotnet/core/extensions/dependency-injection#service-lifetimes)
379+
380+
## IHttpContextAccessor
381+
382+
The [IHttpContextAccessor](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.ihttpcontextaccessor) allows us to access the [HttpContext](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.httpcontext) of the current request from within our resolvers. This is useful, if we for example need to set a header or cookie.
383+
384+
First we need to add the `IHttpContextAccessor` as a service.
385+
386+
```csharp
387+
public class Startup
388+
{
389+
public void ConfigureServices(IServiceCollection services)
390+
{
391+
services.AddHttpContextAccessor();
392+
393+
// Omitted code for brevity
394+
}
395+
}
396+
```
397+
398+
After this we can inject it into our resolvers and make use of the the `HttpContext` property.
399+
400+
```csharp
401+
public string Foo(string id, [Service] IHttpContextAccessor httpContextAccessor)
402+
{
403+
if (httpContextAccessor.HttpContext is not null)
404+
{
405+
// Omitted code for brevity
406+
}
407+
}
408+
```
409+
410+
## IResolverContext
411+
412+
The `IResolverContext` is mainly used in delegate resolvers of the Code-first approach, but we can also access it in the Annotation-based approach, by simply injecting it.
413+
414+
```csharp
415+
public class Query
416+
{
417+
public string Foo(IResolverContext context)
418+
{
419+
// Omitted code for brevity
420+
}
421+
}
422+
```
423+
424+
# Accessing parent values
425+
426+
The resolver of each field on a type has access to the value that was resolved for said type.
427+
428+
Let's look at an example. We have the following schema.
429+
430+
```sdl
431+
type Query {
432+
me: User!;
433+
}
434+
435+
type User {
436+
id: ID!;
437+
friends: [User!]!;
438+
}
439+
```
440+
441+
The `User` schema type is represented by an `User` CLR type. The `id` field is an actual property on this CLR type.
442+
443+
```csharp
444+
public class User
445+
{
446+
public string Id { get; set; }
447+
}
448+
```
449+
450+
`friends` on the other hand is a resolver i.e. method we defined. It depends on the user's `Id` property to compute its result.
451+
From the point of view of this `friends` resolver, the `User` CLR type is its _parent_.
452+
453+
We can access this so called _parent_ value like the following.
454+
455+
<ExampleTabs>
456+
<ExampleTabs.Annotation>
457+
458+
In the Annotation-based approach we can just access the properties using the `this` keyword.
459+
460+
```csharp
461+
public class User
462+
{
463+
public string Id { get; set; }
464+
465+
public List<User> GetFriends()
466+
{
467+
var currentUserId = this.Id;
468+
469+
// Omitted code for brevity
470+
}
471+
}
472+
```
473+
474+
There's also a `[Parent]` attribute that injects the parent into the resolver.
475+
476+
```csharp
477+
public class User
478+
{
479+
public string Id { get; set; }
480+
481+
public List<User> GetFriends([Parent] User parent)
482+
{
483+
// Omitted code for brevity
484+
}
485+
}
486+
```
487+
488+
This is especially useful when using [type extensions](/docs/hotchocolate/defining-a-schema/extending-types).
489+
490+
</ExampleTabs.Annotation>
491+
<ExampleTabs.Code>
492+
493+
```csharp
494+
public class User
495+
{
496+
public string Id { get; set; }
497+
498+
public List<User> GetFriends([Parent] User parent)
499+
{
500+
// Omitted code for brevity
501+
}
502+
}
503+
```
504+
505+
When using the `Resolve` method, we can access the parent through the `IResolverContext`.
506+
507+
```csharp
508+
public class User
509+
{
510+
public string Id { get; set; }
511+
}
512+
513+
public class UserType : ObjectType<User>
514+
{
515+
protected override void Configure(IObjectTypeDescriptor<User> descriptor)
516+
{
517+
descriptor
518+
.Field("friends")
519+
.Resolve(context =>
520+
{
521+
User parent = context.Parent<User>();
522+
523+
// Omitted code for brevity
524+
});
525+
}
526+
}
527+
```
528+
529+
</ExampleTabs.Code>
530+
<ExampleTabs.Schema>
531+
532+
```csharp
533+
public class User
534+
{
535+
public string Id { get; set; }
536+
537+
public List<User> GetFriends([Parent] User parent)
538+
{
539+
// Omitted code for brevity
540+
}
541+
}
542+
```
543+
544+
When using `AddResolver()`, we can access the parent through the `IResolverContext`.
545+
546+
```csharp
547+
services
548+
.AddGraphQLServer()
549+
.AddDocumentFromString(@"
550+
type User {
551+
friends: [User!]!
552+
}
553+
")
554+
.AddResolver("User", "friends", (context) =>
555+
{
556+
User parent = context.Parent<User>();
557+
558+
// Omitted code for brevity
559+
});
560+
```
561+
562+
</ExampleTabs.Schema>
563+
</ExampleTabs>

0 commit comments

Comments
 (0)
Please sign in to comment.