You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -286,8 +286,7 @@ A few points about designing your scalar:
286
286
- Scalar types are expected to be thread safe.
287
287
- 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.
288
288
- Scalar types should be simple and work in isolation.
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.
290
-
- If you have a lot of logic to unpack a string, consider using a regular OBJECT graph type instead.
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.
291
290
- Scalar types should not track any state or depend on any stateful objects.
292
291
-`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.
GraphQL states that when a field returns a value that doesn't conform to the required definition of the field that the value is rejected, converted to null and an error added to the response.
7
+
The GraphQL specification states that when a field resolves a value that doesn't conform to the type expression of the field that the value is rejected, converted to null and an error added to the response.
8
8
9
-
GraphQL ASP.NET makes as few assumptions as possible about the data returned from your fields to result in as few errors as possible.
9
+
When GraphQL ASP.NET build a schema it makes as few assumptions as possible about the data returned from your fields to result in as few errors as possible.
10
10
11
-
These assumptions are made:
11
+
These assumptions are:
12
12
13
13
- Fields that return reference types **can be** null
14
14
- Fields that return value types **cannot be** null
@@ -49,20 +49,20 @@ query {
49
49
</div>
50
50
<br/>
51
51
52
-
This action method could return a `Donut` or returns`null`. But should the donut field, from a GraphQL perspective, allow a null return value? The code certainly does and the rules above say fields that return a reference type can be null...but that's not what's important. Its ultimately your decision to decide if a "null donut" is allowed, not the C# compiler and not the assumptions made by the library.
52
+
This action method could return a `Donut` or return`null`. But should the donut field, from a GraphQL perspective, allow a null return value? The code certainly does and the rules above say fields that return a reference type can be null...but that's not what's important. Its ultimately your decision to decide if a "null donut" is allowed, not the C# compiler and not the assumptions made by the library.
53
53
54
54
On one hand, if a null value is returned, regardless of it being valid, the _outcome_ of the field is the same. When we return a null no child fields are processed. On the other hand, if null is not allowed we need to tell someone, let them know its nulled out not because it simply _is_ null but because a schema violation occurred.
55
55
56
-
## Using an Alternate Type Expression
56
+
## Field Type Expressions
57
57
58
-
Most of the time, using the `TypeExpression` property of a field declaration attribute is sufficient to indicate your intentions.
58
+
You can add more specificity to your fields by using the `TypeExpression` property of the various field declaration attributes.
59
59
60
60
```csharp
61
61
62
62
// Declare that a donut MUST be returned (null is invalid)
> The `TypeExpression` property is available on `[Query]`, `[QueryRoot]`, `[Mutation]`, `[MutationRoot]`, `[Subscription]`, `[SubscriptionRoot]` and `[GraphField]`
95
+
> The value `Type` used in the examples is arbitrary and can be any valid string. The correct type name for the target schema will be used in its place at runtime.
96
96
97
-
## Declaring a TypeDefinition
98
-
99
-
Using the `TypeExpressions` enumeration above is a convenient way to indicate the requirements of a field but in rare occasions you'll need to take it one step further and declare a complete type definition.
100
-
101
-
Take for instance this type:
97
+
<spanstyle="color:pink;">**Warning**: When declared, the runtime will use your `TypeExpression` as law for any field declarations; skipping its internal checks. You can setup a scenario where by you could return data that the runtime could never validate as being correct and GraphQL will happily process it and return an error every time. </span>
102
98
103
99
```csharp
104
-
IEnumerable<IEnumerable<IEnumerable<string>>>
105
-
```
106
-
107
-
The possible scenarios for our data could be endless:
108
-
109
-
- A list of a list of a list of strings
110
-
- A list of a list of a list of strings that can't be null
111
-
- A list that returns a list that could be a list or null that contains a list that contains a string
112
-
- ...and so on
113
-
114
-
For this we have declare the full type definition our self as an array of `MetaGraphType`s
115
-
116
-
```csharp
117
-
// Declare that the the method will return a "list of a list of a list of strings" and that any element could be null
118
-
// This is equivalent to the defaults applied by GraphQL
// GraphQL will attempt to process Donut as an IEnumerable and will fail to resolve every time this
102
+
// field is invoked
103
+
[Query("donut", TypeExpression="[Type]")]
104
+
publicDonutRetrieveDonut(stringid)
138
105
{/*...*/}
139
106
```
140
107
141
-
> The `TypeDefinition` property is available on `[Query]`, `[QueryRoot]`, `[Mutation]`, `[MutationRoot]`, `[Subscription]`, `[SubscriptionRoot]` and `[GraphField]`
108
+
"With great power comes great responsibility" -Uncle Ben
142
109
143
-
**Warning**: When declared, the runtime will accept your `TypeDefinition` or `TypeExpression` as law. You can setup a scenario where by you could return data that the runtime could never validate and GraphQL will happily process it and cause an error every time. For instance returning a single integer but declaring a `TypeDefinition` of a list of integers or declaring a list of donuts but only returning a single instance.
110
+
## Input Argument Type Expressions
111
+
112
+
Similar to fields, you can use the `TypeExpression` property on `[FromGraphQL]` to add more specificity to your input arguments.
144
113
145
114
```csharp
146
-
//ERROR
147
-
//GraphQL will expect an IEnumerable to be returned and will fail every time this
Copy file name to clipboardExpand all lines: docs/controllers/type-extensions.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -68,7 +68,7 @@ Well that's just plain awful. We've over complicated our bakery model and made i
68
68
69
69
## The [TypeExtension] Attribute
70
70
71
-
So what do we do? We've talked in the section on [field paths](./field-paths) about GraphQL maintaining a 1:1 mapping between a field in the graph and a method to retrieve data for it (i.e. its assigned resolver). What prevents us from creating a method to fetch a list of Cake Orders and saying, "Hey, GraphQL! When someone asks for the field `[type]/bakery/orders` call our method instead of a property getter on the `Bakery` class. As it turns out, that is exactly what a `Type Extension` does.
71
+
So what do we do? We've talked before about GraphQL maintaining a 1:1 mapping between a field in the graph and a method to retrieve data for it (i.e. its assigned resolver). What prevents us from creating a method to fetch a list of Cake Orders and saying, "Hey, GraphQL! When someone asks for the field `[type]/bakery/orders` call our method instead of a property getter on the `Bakery` class. As it turns out, that is exactly what a `Type Extension` does.
Copy file name to clipboardExpand all lines: docs/controllers/virtual-types.md
+34-45Lines changed: 34 additions & 45 deletions
Original file line number
Diff line number
Diff line change
@@ -1,72 +1,61 @@
1
1
---
2
-
id: field-paths
2
+
id: virtual-types
3
3
title: Virtual Graph Types
4
4
sidebar_label: Virtual Graph Types
5
5
---
6
6
7
7
## What is a Virtual Graph Type?
8
8
9
-
When we reason about ASP.NET MVC, routing comes naturally. We define a URL and perform an HTTP request to fetch data.
9
+
GraphQL is statically typed. Each field in a query must always resolve to a single graph type known to the schema. This can make query organization rather tedious and adds A LOT of boilerplate code if you wanted to introduce even the slightest complexity to your graph.
10
10
11
-
```
12
-
GET http://homeMadeBakery.local/pastries/donuts/15
13
-
```
14
-
15
-
and we can picture how this might map to an API Controller:
16
-
17
-
```csharp
18
-
[Route("pastries")]
19
-
publicclassPastriesController : Controller
20
-
{
21
-
[HttpGet("donuts/{id}")]
22
-
publicDonutRetrieveDonut(intid)
23
-
{/* ... */}
24
-
}
25
-
```
26
-
27
-
When you startup an MVC or Web API application .NET gathers your configured routes and maintains a map of `endpoint -> action method` in order to hand off an HTTP request to a given method.
28
-
29
-
OK, Great! Now lets think about this graphQL query:
11
+
Let's think about this query:
30
12
31
-
```javascript
13
+
```ruby
32
14
query {
33
-
pastries {
34
-
donut(id:15){
35
-
name
36
-
flavor
15
+
groceryStore {
16
+
bakery {
17
+
pastries {
18
+
donut(id:15){
19
+
name
20
+
flavor
21
+
}
22
+
}
37
23
}
24
+
deli {
25
+
meats {
26
+
beef (id:23) {
27
+
name
28
+
cut
29
+
}
30
+
}
31
+
}
38
32
}
39
33
}
40
34
```
41
35
42
-
We can think about each field in the query as a unique path in our schema, similar to the URL above:
43
-
44
-
```ruby
45
-
[query]
46
-
[query]/pastries
47
-
[query]/pastries/donut
48
-
[type]/donut/name
49
-
[type]/donut/flavor
50
-
```
51
-
52
-
In fact, this is how GraphQL ASP.NET knows how to invoke your methods. At startup, a complete map of the query and mutation methods as well as every graph type is translated into a set of field paths. Then, when a request is made of a field, it invokes the method or property associated with that `field path`.
36
+
Knowing what we know about GraphQL's requirements, we need to create types for the grocery store, the bakery, pastries, a donut, the deli counter, meats, beef etc. Its a lot of setup for what basically boils down to two methods to retrieve a donut and a cut of beef by their respective ids.
53
37
54
-
> All action methods, graph type properties and graph type methods must map to a unique field path in your graph schema.
38
+
This is where `virtual graph types` come in. Using a templating pattern similar to what we do with REST queries we can create rich graphs with very little boiler plate. Adding a new arm to your graph is as simple as defining a path to it in a controller.
This is a different controller than in the top example. We inherit from `GraphController` and use `Query` instead of `Controller` and `HttpGet`, respectively.
54
+
Internally, for each encountered path segment (e.g. `bakery`, `meats`), GraphQL generates a `virutal graph type` to fulfill resolver requests for you and act as a pass through to your real code. It does this in concert with your real code and performs a lot of checks at start up to ensure that the combination of your real types as well as virutal types can be put together to form a functional graph. If a collision occurs the server will fail to start.
55
+
56
+
#### Another Example
68
57
69
-
You can nest fields as deep as you need in order to create a rich organizational hierarchy to your data. This is best explained by code, take a look at these two controllers:
58
+
You can nest fields as deep as you want and spread them across any number of controllers in order to create a rich organizational hierarchy to your data. This is best explained by code, take a look at these two controllers:
70
59
71
60
```csharp
72
61
@@ -143,7 +132,7 @@ With REST, this is probably 4 separate requests or one super contrived request b
143
132
144
133
## Actions Must Have a Unique Path
145
134
146
-
As was said above, each field in your object graph must uniquely map to one method (or getter property) in your code.
135
+
Each field in your object graph must uniquely map to one method (or getter property) in your code.
147
136
148
137
Take this example:
149
138
@@ -385,7 +374,7 @@ When you start thinking about large object graphs, 100s of controllers and 100s
385
374
386
375
## Field Path Names
387
376
388
-
> Each segment of a field path must individually conform to the required naming standards for fields and graph type names.
377
+
> Each segment of a virtual field path must individually conform to the required naming standards for fields and graph type names.
389
378
390
379
In reality this primarily means don't start your fields with a double underscore, `__`, as thats reserved by the introspection system. The complete regex is available in the source code at `Constants.RegExPatterns.NameRegex`.
Copy file name to clipboardExpand all lines: docs/development/unit-testing.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@ title: Unit Testing
4
4
sidebar_label: Unit Testing
5
5
---
6
6
7
-
GraphQL ASP.NET has more than `2500 unit tests and 91% code coverage`. Much of this is powered by a test component designed to quickly build a configurable, fully mocked server instance to perform a query. It may be helpful to download the code and extend it for harnessing your own controllers.
7
+
GraphQL ASP.NET has more than `3000 unit tests and 91% code coverage`. Much of this is powered by a test component designed to quickly build a configurable, fully mocked server instance to perform a query. It may be helpful to download the code and extend it for harnessing your own controllers.
8
8
9
9
The `TestServerBuilder<TSchema>` can be found in the `graphql-aspnet-testframework` project of the primary repo and is dependent on `Moq`. As its part of the core library solution you'll want to remove the project reference to `graphql-aspnet` project and instead add a reference to the nuget package.
0 commit comments