Skip to content

Commit f9d3f19

Browse files
Added Missing Directive Attributes and Clarified content (#18)
* updated attributes, actions, scalars, controller actions * updated custom scalars documentation * Added missing directive attributes * Typos and code sample updates * word choice updates to several controller related docs
1 parent e4d5875 commit f9d3f19

File tree

9 files changed

+36
-34
lines changed

9 files changed

+36
-34
lines changed

docs/controllers/authorization.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ title: Authorization
44
sidebar_label: Authorization
55
---
66

7+
## Quick Examples
8+
79
If you've wired up ASP.NET authorization before, you'll likely familiar with the `[Authorize]` attribute and how its used to enforce security. GraphQL ASP.NET works the same way.
810

911
```csharp
@@ -43,7 +45,7 @@ public class BakeryController : GraphController
4345
}
4446
```
4547

46-
GraphQL supports nested checks at Controller and Action levels.
48+
This library supports nested policy and role checks at Controller and Action levels.
4749

4850
```csharp
4951
// BakeryController.cs
@@ -76,9 +78,9 @@ public class BakeryController : GraphController
7678
}
7779
```
7880

79-
## GraphQL Uses IAuthorizationService
81+
## Use of IAuthorizationService
8082

81-
Under the hood, GraphQL taps into the your `IServiceProvider` to obtain a reference to the `IAuthorizationService` that gets created when you configure `.AddAuthorization()`. Take a peek at the GraphQL FieldAuthorizer in the source code for the full picture.
83+
Under the hood, GraphQL taps into your `IServiceProvider` to obtain a reference to the `IAuthorizationService` that gets created when you configure `.AddAuthorization()` for policy enforcement rules. Take a look at the [Field Authorization](https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Middleware/FieldSecurity/Components/FieldAuthorizationMiddleware.cs) Middleware Component for the full picture.
8284

8385
## When does Authorization Occur?
8486

@@ -88,7 +90,7 @@ _The Default "per field" Authorization workflow_
8890

8991
---
9092

91-
In the diagram above we can see that user authorization in GraphQL ASP.NET makes use of the result from [ASP.NET's security pipeline](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction?view=aspnetcore-3.0) but makes no attempt to interact with it. Whether you use Kerberos tokens, OAUTH2, username/password, API tokens or if you support 2-factor authentication or one-time-use passwords, GraphQL doesn't care. The entirety of your authentication and authorization pipeline is executed completely before GraphQL gets involved.
93+
In the diagram above we can see that user authorization in GraphQL ASP.NET makes use of the result from [ASP.NET's security pipeline](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) but makes no attempt to interact with it. Whether you use Kerberos tokens, OAUTH2, username/password, API tokens or if you support 2-factor authentication or one-time-use passwords, GraphQL doesn't care. The entirety of your authentication and authorization pipeline is executed by GraphQL, no special arrangements or configuration is needed.
9294

9395
> GraphQL ASP.NET draws from your configured authentication/authorization solution.
9496
@@ -98,7 +100,7 @@ Null propagation rules still apply to unauthorized fields meaning if the field c
98100

99101
By default, a single unauthorized result does not necessarily kill an entire query, it depends on the structure of your object graph. When a field request is terminated any down-stream child fields are discarded immediately but sibling fields or unrelated ancestors continue to execute as normal.
100102

101-
Since this authorization occurs "per field" and not "per controller action" its possible to define the same security chain for POCO properties. This allows you to effectively deny access, by policy, to a single property of a single instantiated object if you wish. Performing security checks for every field of data (especially in parent/child scenarios) has a performance cost though, especially for larger data sets. For most scenarios enforcing security at the controller level is sufficient.
103+
Since this authorization occurs "per field" and not "per controller action" its possible to define the same security chain for POCO properties. This allows you to effectively deny access, by policy, to a single property of an instantiated object. Performing security checks for every field of data (especially in parent/child scenarios) has a performance cost though, especially for larger data sets. For most scenarios enforcing security at the controller level is sufficient.
102104

103105
## Authorization Failures are Obfuscated
104106

docs/controllers/batch-operations.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ sidebar_label: Batch Operations
88
99
## The N+1 Problem
1010

11-
There are plenty of articles on the web discussing the theory behind the N+1 problem (links below) Instead, we'll jump into an into an example to illustrate the issue when it comes to GraphQL.
11+
There are plenty of articles on the web discussing the theory behind the N+1 problem ([links below](./batch-operations#other-resources)). Instead, we'll jump into an into an example to illustrate the issue when it comes to GraphQL.
1212

1313
Let's build on our example from the discussion on type extensions where we created an extension to retrieve `Cake Orders` for a **single** `Bakery`. What if we're a national chain and need to see the last 50 orders for each of our stores in a region? This seems like a reasonable thing an auditor would do so lets alter our controller to fetch all our bakeries and then let our type extension fetch the cake orders.
1414

@@ -51,11 +51,11 @@ Well that was easy, right? Not so fast. The `bakeries` field returns a `List<Bak
5151

5252
This is the N+1 problem. `1 query` for the bakeries + `N queries` for the cake orders, where N is the number of bakeries first retrieved.
5353

54-
If we could _batch_ the cake orders request and fetch all the orders for all the bakeries at once, then assign the `Cake Orders` back to their bakeries, we'd be a lot better off. No matter the number of bakeries retrieved, we'd execute 2 queries; 1 for `bakeries` and 1 for `orders`.
54+
If we could _batch_ the cake orders request and fetch all the orders for all the bakeries at once, then assign the `Cake Orders` back to their respective bakeries, we'd be a lot better off. No matter the number of bakeries retrieved, we'd execute 2 queries; 1 for `bakeries` and 1 for `orders`.
5555

5656
## Data Loaders
5757

58-
You'll often hear the term `Data Loaders` when reading about GraphQL implementations. Methods that load the child data being requested as a single operation before assigning to each of the parents. There is no difference with GraphQL ASP.NET. You still have to write that method. But with the ability to capture action method parameters and clever use of an `IGraphActionResult` we can combine the data load phase with the assignment phase into a single `batch operation`, at least on the surface. The aim is to make it easy to read and easier to write.
58+
You'll often hear the term `Data Loaders` when reading about GraphQL implementations. Methods that load the child data being requested as a single operation before assigning to each of the parents. There is no difference with GraphQL ASP.NET. You still have to write that method. But with the ability to capture action method parameters and clever use of an `IGraphActionResult` we can combine the data load phase with the assignment phase into a single batch operation, at least on the surface. The aim is to make it easy to read and easier to write.
5959

6060
## The [BatchTypeExtension] Attribute
6161

docs/controllers/field-paths.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
id: field-paths
3-
title: Field Paths
4-
sidebar_label: Field Paths
3+
title: Virtual Graph Types
4+
sidebar_label: Virtual Graph Types
55
---
66

7-
## What is Field Pathing?
7+
## What is a Virtual Graph Type?
88

99
When we reason about ASP.NET MVC, routing comes naturally. We define a URL and perform an HTTP request to fetch data.
1010

@@ -223,11 +223,11 @@ public class BakeryController : GraphController
223223
}
224224
```
225225

226-
Since both methods map to a field path of `[mutation]/bakery/orderDonuts` this would cause a `GraphTypeDeclarationException` to be thrown when your application starts.
226+
From a GraphQL perspective this equivilant to trying to define a `bakery` type with two fields named `orderDonuts`. Since both methods map to a field path of `[mutation]/bakery/orderDonuts` this would cause a `GraphTypeDeclarationException` to be thrown when your application starts.
227227

228228
With MVC the ASP.NET runtime could inspect any combinations of parameters passed on the query string or the POST body to work out which overload to call. You might be thinking, why can't GraphQL inspect the passed input arguments and make the same determination?
229229

230-
In some cases it probably could. But looking at this example we run into an issue:
230+
Putting aside that it [violates the specification](http://spec.graphql.org/October2021/#sec-Objects), in some cases it probably could. But looking at this example we run into an issue:
231231

232232
```csharp
233233
// C# Controller

docs/controllers/type-extensions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Well that's just plain awful. We've over complicated our bakery model and made i
6868

6969
## The [TypeExtension] Attribute
7070

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

7373
```csharp
7474
// Bakery.cs
@@ -101,7 +101,7 @@ There is a lot to unpack here, so lets step through it:
101101
- The method returns `List<CakeOrder>` as the type of data it generates.
102102
- The method takes in a `Bakery` instance (more on that in a second) as well as an integer, with a default value of `15`, to limit the number of orders to retrieve.
103103

104-
Now we can query the `orders` field from anywhere a bakery is returned in the object graph and GraphQL will invoke our method instead searching for a property named`Bakery.Orders`.
104+
Now we can query the `orders` field from anywhere a bakery is returned in the object graph and GraphQL will invoke our method instead searching for a property named `Bakery.Orders`.
105105

106106
```javascript
107107
query {
@@ -119,7 +119,7 @@ query {
119119
120120
#### But what about the Bakery parameter?
121121

122-
When you declare a `type extension` it will only be invoked in context of the type being extended.
122+
When you declare a type extension it will only be invoked in context of the type being extended.
123123

124124
When we return a field of data from a property, an instance of the object must to exist in order to retrieve the property value. The same is true for a `type extension` except that instead of calling a property getter on the instance we're handing the entire instance to our method and letting it figure out what it needs to do with the data to resolve the field.
125125

docs/reference/attributes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ method should be invoked for a particular location.
148148
public sealed class AllowFragment : GraphDirective
149149
{
150150
[DirectiveLocations(ExecutableDirectiveLocation.FRAGMENT_SPREAD | ExecutableDirectiveLocation.INLINE_FRAGMENT)]
151-
public IGraphActionResult BeforeFieldResolution([FromGraphQL("if")] bool ifArgument)
151+
public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)
152152
{
153153
return ifArgument ? this.Ok() : this.Cancel();
154154
}
@@ -169,7 +169,7 @@ during `SchemaGeneration` and `AfterFieldResolution` depending on the allowed ta
169169
public sealed class AllowFragment : GraphDirective
170170
{
171171
[DirectiveLocations(ExecutableDirectiveLocation.FIELD)]
172-
public IGraphActionResult BeforeFieldResolution([FromGraphQL("if")] bool ifArgument)
172+
public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)
173173
{
174174
return ifArgument ? this.Ok() : this.Cancel();
175175
}

docs/types/input-objects.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class Donut
4141

4242
```
4343
// GraphQL Type Definition
44-
type NewDonutModel {
44+
input NewDonutModel {
4545
id: Int!
4646
name: String
4747
type: DonutType!
@@ -116,12 +116,9 @@ type Input_Donut {
116116
</div>
117117
<br/>
118118

119-
120-
> *Note:* `KeyValuePair<T, K>` is a commonly used struct. While it can be safely used as an `OBJECT` type, it should not be used as an `INPUT_OBJECT`. This is because, [by definition](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/KeyValuePair.cs), `KeyValuePair<T,K>` does not declare publicly accessible setters on the `Key` and `Value` properties. Attempting to use the struct as an `INPUT_OBJECT` will not fail, but it will also not include any fields making it mostly useless.
121-
122119
## Methods are Ignored
123120

124-
While its possible to have methods be exposed as fields on regular `OBJECT` types they are ignored for input arguments regardless of the declaration rules applied to the type.
121+
While its possible to have methods be exposed as fields on regular `OBJECT` types they are ignored for input types regardless of the declaration rules applied to the type.
125122

126123
<div class="sideBySideCode hljs">
127124
<div>
@@ -146,9 +143,10 @@ public class Donut
146143
</div>
147144
<div>
148145

149-
```
150-
// GraphQL Type Definition
151-
type Input_Donut {
146+
```ruby
147+
# GraphQL Type
148+
# Definition
149+
input Input_Donut {
152150
id: Int!
153151
name: String
154152
type: DonutType!
@@ -162,7 +160,7 @@ type Input_Donut {
162160

163161
## Working With Lists
164162

165-
When constructing a set of items as an input value, GraphQL will instantiate a `List<T>` and fill it with the appropriate data, be that another list, another input object or a scalar. While you can declare a regular array (e.g. `Donut[]`, `int[]` etc.) as your list structure for an input argument, graphql has to rebuild its internal list structure as an array (or nested arrays) to meet the requirements of your method. In some cases, especially with nested lists, this result in an `O(n)` increase in processing time. It is recommended to use `IEnumerable<T>` or `IList<T>` to avoid this performance bottleneck when sending a lot of items as input arguments.
163+
When constructing a set of items as an input value, GraphQL will instantiate a `List<T>` and fill it with the appropriate data, be that another list, another input object or a scalar. While you can declare a regular array (e.g. `Donut[]`, `int[]` etc.) as your list structure for an input argument, graphql has to rebuild its internal list structure as an array (or nested arrays) to meet the requirements of your method. In some cases, especially with nested lists, this results in a linear increase in processing time. It is recommended to use `IEnumerable<T>` or `IList<T>` to avoid this performance bottleneck when sending a lot of items as input arguments.
166164

167165
This example shows various ways of accepting collections of data as inputs to controller actions.
168166

docs/types/interfaces.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Most of the time GraphQL is smart enough to figure out which object types you're
5454

5555
## Use It To Include It
5656

57-
When GraphQL ASP.NET starts building a schema it will read the interfaces attached to any model classes it finds and stage them in a special holding area. However, unless an interface is actually referenced as a return value of a field, be it from an action method or a model property, it won't be added to your schema and won't be visible to introspection queries. That is to say that when you register `Donut`, unless you specifically return `IPastry` from your application, GraphQL will leave it out of the schema. This goes a long ways in preventing clutter in your schema with all the interfaces you may declare internally.
57+
When GraphQL starts building a schema it will read the interfaces attached to any model classes it finds and stage them in a special holding area. However, unless an interface is actually referenced as a return value of a field, be it from an action method or a model property, it won't be added to your schema and won't be visible to introspection queries. That is to say that when you register `Donut`, unless you specifically return `IPastry` from your application, GraphQL will leave it out of the schema. This goes a long ways in preventing clutter in your schema with all the interfaces you may declare internally.
5858

5959
<div class="sideBySideCode hljs">
6060
<div>
@@ -92,6 +92,7 @@ type Donut {
9292
</div>
9393
</div>
9494

95+
<br/>
9596
Of course if you call `.AddGraphType<IPastry>()` during [schema configuration](../reference/schema-configuration) GraphQL will happily publish the type even if its never used in the graph.
9697

9798
## Interfaces are not Input Objects

docs/types/objects.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ sidebar_label: Objects
66

77
The `object graph type` is one of six fundamental types defined by GraphQL. We can think of a graph query like a tree and if [scalar values](./scalars), such as `string` and `int`, are the leafs then objects are the branches.
88

9-
In GraphQL ASP.NET a C# `class` is used to identify an `object` in a schema.
9+
In GraphQL ASP.NET a C# `class` or `struct` is used to identify an `OBJECT` type in a schema.
1010

1111
Here we've defined a `donut` model class. The runtime will convert it, automatically, into a graph type. If you're familiar with GraphQL's own type definition language the equivalent expression is shown to the right.
1212

@@ -40,6 +40,7 @@ type Donut {
4040
</div>
4141
</div>
4242

43+
<br/>
4344
\*The `DonutType` enumeration is covered in the [enum type](./enums) section.
4445

4546
By Default, object graph types:
@@ -211,8 +212,8 @@ GraphQL will follow a cascading model of inclusion rules. Indicating a rule on t
211212

212213
By Default, GraphQL won't include your class in a schema unless:
213214

214-
- Its referenced in a controller.
215-
- Referenced by a graph type that is referenced in a controller.
215+
- Its referenced in a controller OR
216+
- Referenced by a graph type that is referenced in a controller OR
216217
- Tagged with `[GraphType]`.
217218

218219
But schema configurations can override this behavior and allow GraphQL to greedily include classes that it'll never use. This can expose them in an introspection query unintentionally. You can flag a class such that auto-inclusion will be skipped unless GraphQL can determine that the object is required to fulfill a request to the schema.
@@ -257,4 +258,4 @@ The usage of `struct` types as an `OBJECT` graph type is fully supported. The sa
257258

258259
## Reuse as Input Objects
259260

260-
Both `class` and `struct` types can be used as an `INPUT_OBJECT` and an output `OBJECT` graph type. See the section on [input objects](./input-objects) for some of the key differences and requirements.
261+
Both `class` and `struct` types can be used as an `INPUT_OBJECT` and an `OBJECT` graph type. See the section on [input objects](./input-objects) for some of the key differences and requirements.

docs/types/unions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public class SaladOrBread : GraphUnionProxy
121121
122122
## Union Name Uniqueness
123123

124-
Union names must be unique in a schema. If you do declare a union in multiple action methods without a proxy, GraphQL will attempt to validate the references by name and included types. As long as all declarations are the same, that is the name and the set of included types, then there graphql will accept the union. Otherwise, a `GraphTypeDeclarationException` will be thrown at startup.
124+
Union names must be unique in a schema. If you do declare a union in multiple action methods without a proxy, GraphQL will attempt to validate the references by name and included types. As long as all declarations are the same, that is the name and the set of included types, then graphql will accept the union. Otherwise, a `GraphTypeDeclarationException` will be thrown at startup.
125125

126126
## Liskov Substitutions
127127

@@ -225,7 +225,7 @@ public class BakeryController : GraphController
225225
```
226226

227227
> Note: `MapType` is not based on the resolved field value, but only on the `System.Type`. This is by design to guarantee consistency in query execution.
228-
> If your returned type causes the query to remain indeterminate a validation error (rule 6.4.3) will be applied to the query.
228+
> If your returned type causes the query to remain indeterminate a validation error (rule [6.4.3](https://spec.graphql.org/October2021/#sec-Value-Completion)) will be applied to the query.
229229
230230
The query will now interpret all `Bagels` as `Rolls` and be able to process the query correctly.
231231

0 commit comments

Comments
 (0)