Skip to content

Commit 36de411

Browse files
v0.12.0-beta Input Object Updates (#22)
* updated input object documentation * additional updates to action results, action methods
1 parent 141e77b commit 36de411

File tree

4 files changed

+220
-52
lines changed

4 files changed

+220
-52
lines changed

docs/advanced/graph-action-results.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class BakeryController : Controller
4040

4141
You can either return the data itself or some alternate `IActionResult` to tell MVC how to render a response.
4242

43-
Common Action Results for MVC:
43+
Some common ASP.NET MVC action results:
4444

4545
- `this.Ok()` : Everything worked fine, return status 200.
4646
- `this.NotFound()` : The item doesn't exist, return status 404.
@@ -49,7 +49,7 @@ Common Action Results for MVC:
4949

5050
This works the same way in GraphQL ASP.NET. The available actions are slightly different (GraphQL won't stream files) but the usage is the same. You can even write your own action results.
5151

52-
## Common Graph Action Results
52+
## Common Action Results
5353

5454
Instead of `IActionResult` we use `IGraphActionResult` from a controller action method. Both [directives](../directives) and controller [action methods](../controllers/actions) can return action results.
5555

@@ -62,9 +62,13 @@ Built in Controller Action Methods:
6262
- `this.BadRequest()`: Commonly used in conjunction with `this.ModelState`. This result indicates the data supplied to the method is not valid for the operation. If given the model state collection an error for each validation error is rendered.
6363
- `this.InternalServerError()`: Indicates an unintended error, such as an exception occurred. The supplied message will be added to the response and no child fields will be resolved.
6464

65-
[Directives](../directives) have one Additional Action Result:
65+
## Directive Action Results
66+
[Directives](../directives) have two built in Action Results:
6667

67-
- `this.Cancel()`: When returned as part of a method that executes before field resolution, this action will cancel the field execution pipeline. No error is returned, but the field is dropped from the request.
68+
- `this.Ok()`: Indicates that the directive completed its expected operation successfully and query processing can continue.
69+
- `this.Cancel()`: Indicates that the directive did NOT complete its operation successfully.
70+
- If this is a type system directive, the schema will fail to complete and the server will not start.
71+
- If this is an execution directive, the query will be abandoned and the user will receive an error message.
6872

6973
## Custom Graph Action Results
7074

docs/controllers/actions.md

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ query {
418418
</div>
419419
<br/>
420420

421-
Note that there is a difference between "nullable" and "not required". If we have a nullable int as an input parameter, without a default value we still have to pass it to the field, even if we pass it as null.
421+
Note that there is a difference between "nullable" and "not required". If we have a nullable int as an input parameter, without a default value we still have to pass it to the field, even if we pass it as null just like if we were to invoke the method from our C# code.
422422

423423
<div class="sideBySideCode hljs">
424424
<div>
@@ -491,6 +491,53 @@ query {
491491
</div>
492492
</div>
493493

494+
### Working With Lists
495+
496+
When constructing a set of items as an argument to an action method, GraphQL will instantiate a `List<T>` and fill it with the appropriate data, be that another list, another input object, a scalar etc. While you can declare an array (e.g. `Donut[]`, `int[]` etc.) as your list structure for an input argument, graphql has to rebuild its internal representation 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 lots of items as input data.
497+
498+
This example shows various ways of accepting collections of data as inputs to controller actions.
499+
500+
```csharp
501+
public class BakeryController : GraphController
502+
{
503+
// a list of donuts
504+
// schema syntax: [Donut]
505+
[Mutation("createDonuts")]
506+
public bool CreateDonuts(IEnumerable<Donut> donuts)
507+
{/*....*/}
508+
509+
// when used as a "list of list"
510+
// schema syntax: [[Donut]]
511+
[Mutation("createDonutsBySet")]
512+
public bool CreateDonuts(List<List<Donut>> donuts)
513+
{/*....*/}
514+
515+
// when supplied as a regular array
516+
// schema syntax: [Donut]
517+
[Mutation("donutsAsAnArray")]
518+
public bool DonutsAsAnArray(Donut[] donuts)
519+
{/*....*/}
520+
521+
// This is a valid nested list
522+
// schema syntax: [[[Donut]]]
523+
[Mutation("mixedDonuts")]
524+
public bool MixedDonuts(List<IEnumerable<Donut[]>> donuts)
525+
{/*....*/}
526+
527+
// when used as a field of another input object
528+
[Mutation("createDonutCollection")]
529+
public bool CreateDonuts(DonutCollection donutCollection)
530+
{/*....*/}
531+
532+
}
533+
534+
public class DonutCollection
535+
{
536+
public List<Donut> Donuts { get; set; }
537+
}
538+
539+
```
540+
494541
### Don't Use Dictionaries
495542

496543
You might be tempted to use a dictionary as a parameter to accept arbitrary key value pairs into your methods. GraphQL will reject it and throw a declaration exception when your schema is created:
@@ -501,7 +548,7 @@ You might be tempted to use a dictionary as a parameter to accept arbitrary key
501548
```csharp
502549
public class BakeryController : GraphController
503550
{
504-
// ERROR, a GraphDeclarationException
551+
// ERROR, a GraphTypeDeclarationException
505552
// will be thrown.
506553
[QueryRoot]
507554
public IEnumerable<Donut>
@@ -515,7 +562,7 @@ public class BakeryController : GraphController
515562

516563
```javascript
517564
query {
518-
searchDonuts(
565+
searchDonuts(searchParams:
519566
name: "jelly*"
520567
filled: true
521568
dayOld: false){
@@ -529,7 +576,7 @@ query {
529576
</div>
530577
<br/>
531578

532-
At runtime, GraphQL will try to validate every parameter passed on a query against the type expression it has stored in the target schema. No where have we we declared `filled` to be a boolean or `name` to be a string.
579+
At runtime, GraphQL will try to validate every parameter passed on a query against the type expression it has stored in the target schema. No where have we declared an argument `filled` to be a boolean or `name` to be a string.
533580

534581
One might think, well it should be passed as an object reference to the dictionary parameter:
535582

docs/types/input-objects.md

Lines changed: 160 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,18 @@ title: Input Objects
44
sidebar_label: Input Objects
55
---
66

7-
`INPUT_OBJECT` graph types function in much the same way as [object graph types](./objects) do.
8-
While GraphQL is doing its discovery of controllers and graph types, whenever it comes across a class used as a parameter to a method it will attempt to generate the appropriate input type definition.
7+
`INPUT_OBJECT` graph types (a.k.a. input objects) represent complex data supplied to arguments on fields or directives. Anytime you want to pass more data than a single string or a number, perhaps an Address or a new Employee, you use an INPUT_OBJECT to represent that entity in GraphQL. When the system scans your controllers, if it comes across a class used as a parameter to a method it will attempt to generate the appropriate input type definition to represent that class.
98

10-
The rules surrounding naming, field declarations, exclusions, use of `[GraphSkip]` etc. apply to input objects but with a few key differences.
9+
The rules surrounding naming, field declarations, exclusions, use of `[GraphSkip]` etc. apply to input objects but with a few key differences:
1110

12-
By Default:
13-
14-
- An input object is named the same as its `class` name, prefixed with `Input_`
15-
- i.e. `Input_Donut`, `Input_Employee`
16-
- All public properties with a `get` and `set` statement will be included.
17-
- The return type of the property must be an acceptable type or it will be skipped
11+
- Unless overridden, an input object is named the same as its class name, prefixed with `Input_` (e.g. `Input_Address`, `Input_Employee`)
12+
- Only public properties with a `get` and `set` will be included.
13+
- Property return types cannot be `Task<T>`, an `interface` and cannot implement `IGraphUnionProxy` or `IGraphActionResult`. Such properties are always skipped.
1814
- Methods are always skipped.
1915

20-
## Names
16+
## Customized Type Names
2117

22-
Input object types can be given customized names, just like with object types, using the `[GraphType]` attribute.
18+
Input objects can be given customized names, just like with object types, using the `[GraphType]` attribute.
2319

2420
<div class="sideBySideCode hljs">
2521
<div>
@@ -53,6 +49,8 @@ input NewDonutModel {
5349
</div>
5450
<br/>
5551

52+
>Not the specific callout to `InputName` in the attribution.
53+
5654
## Use an Empty Constructor
5755

5856
When GraphQL executes a query it will attempt to create an instance of your input object then assign the key/value pairs received on the query to the properties. In order to do the initial instantiation it requires a public parameterless constructor to do so.
@@ -78,7 +76,7 @@ public class DonutModel
7876
}
7977
```
8078

81-
Because of this restriction it can be helpful to separate your classes between "input" and "output" types much is the same way we do with `ViewModel` vs. `BindingModel` objects in MVC. This is optional, mix and match as needed by your use case.
79+
Because of this restriction it can be helpful to separate your classes between "input" and "output" types much is the same way we do with `ViewModel` vs. `BindingModel` objects with REST queries in ASP.NET. This is optional, mix and match as needed by your use case.
8280

8381
## Properties Must Have a Public Setter
8482

@@ -128,7 +126,8 @@ While its possible to have methods be exposed as resolvable fields on regular `O
128126
public class Donut
129127
{
130128
[GraphField("salesTax")]
131-
public decimal CalculateSalesTax(decimal taxPercentage)
129+
public decimal CalculateSalesTax(
130+
decimal taxPercentage)
132131
{
133132
return this.Price * taxPercentage;
134133
}
@@ -158,49 +157,167 @@ input Input_Donut {
158157
</div>
159158
<br/>
160159

161-
## Working With Lists
160+
## Required Fields And Default Values
161+
Add `[Required]` (from System.ComponentModel) to any property to force a user to supply the field in a query document.
162162

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.
163+
Any non-required field will automatically be assigned a default value if not supplied. This default value is equivilant to the property value of the object when its instantiated via its public, parameterless constructor.
164164

165-
This example shows various ways of accepting collections of data as inputs to controller actions.
165+
<div class="sideBySideCode hljs">
166+
<div>
166167

167168
```csharp
168-
public class BakeryController : GraphController
169+
// Donut.cs
170+
public class Donut
169171
{
170-
// a list of donuts
171-
// schema syntax: [Donut]
172-
[Mutation("createDonuts")]
173-
public bool CreateDonuts(IEnumerable<Donut> donuts)
174-
{/*....*/}
172+
public Donut()
173+
{
174+
// set custom defaults if needed
175+
this.Type = DonutType.Vanilla;
176+
this.Price = 2.99;
177+
this.IsAvailable = true;
178+
}
175179

176-
// when used as a "list of list"
177-
// schema syntax: [[Donut]]
178-
[Mutation("createDonutsBySet")]
179-
public bool CreateDonuts(List<List<Donut>> donuts)
180-
{/*....*/}
180+
[Required]
181+
public string Name { get; set; }
181182

182-
// when supplied as a regular array
183-
// schema syntax: [Donut]
184-
[Mutation("donutsAsAnArray")]
185-
public bool DonutsAsAnArray(Donut[] donuts)
186-
{/*....*/}
183+
public int Id { get; set; }
184+
public DonutType Type { get; set; }
185+
public Bakery Bakery { get;set; }
186+
public decimal Price { get; set; }
187+
public decimal IsAvailable { get; set; }
188+
}
189+
```
187190

188-
// This is a valid nested list
189-
// schema syntax: [[[Donut]]]
190-
[Mutation("mixedDonuts")]
191-
public bool MixedDonuts(List<IEnumerable<Donut[]>> donuts)
192-
{/*....*/}
191+
</div>
192+
<div>
193193

194-
// when used as a field of another input object
195-
[Mutation("createDonutCollection")]
196-
public bool CreateDonuts(DonutCollection donutCollection)
197-
{/*....*/}
194+
```ruby
195+
# GraphQL Type Definition
196+
input Input_Donut {
197+
name: String!
198+
id: Int! = 0
199+
type: DonutType! = VANILLA
200+
bakery: Input_Bakery = null
201+
price: Decimal! = 2.99
202+
isAvailable: Boolean! = true
203+
}
204+
```
205+
206+
</div>
207+
</div>
208+
<br/>
209+
210+
## Non-Nullability
211+
By default, all properties that are reference types (i.e. classes) are nullable and all value types (primatives, structs etc.) are non-nullable
212+
213+
214+
<div class="hljs">
215+
216+
```csharp
217+
// Donut.cs
218+
public class Bakery
219+
{
220+
// a reference to another object
221+
public Person Owner { get; set; }
222+
}
223+
```
224+
225+
```ruby
226+
# GraphQL Type Definition
227+
input Input_Donut {
228+
owner: Input_Person = null
229+
}
230+
```
231+
232+
</div>
233+
<br/>
234+
235+
If you want to force a value to be supplied (either on a query document or by default) you can use the [GraphField] attribute to augment the field.
198236

237+
238+
<div class="hljs">
239+
240+
```csharp
241+
// Donut.cs
242+
public class Bakery
243+
{
244+
public Bakery()
245+
{
246+
this.Owner = new Person("Bob Smith");
247+
}
248+
249+
// a reference to another object
250+
[GraphField(TypeExpression = TypeExpressions.IsNotNull)]
251+
public Person Owner { get; set; }
252+
}
253+
```
254+
```ruby
255+
# GraphQL Type Definition
256+
input Input_Donut {
257+
owner: Input_Person! = { name: "Bob Smith" }
258+
}
259+
```
260+
</div>
261+
<br/>
262+
263+
264+
> Any field explicitly or implicitly declared as non-nullable, that is not required, MUST have a default value assigned to it that is not `null`.
265+
266+
267+
Add the [Required] attribute to force a user to supply a non-null value for the field on a query document.
268+
269+
270+
<div class="hljs">
271+
272+
```csharp
273+
// Donut.cs
274+
public class Bakery
275+
{
276+
public Bakery()
277+
{
278+
}
279+
280+
// a reference to another object
281+
[Required]
282+
[[GraphField(TypeExpression = TypeExpressions.IsNotNull)]]
283+
public Person Owner { get; set; }
199284
}
285+
```
286+
```ruby
287+
# GraphQL Type Definition
288+
input Input_Donut {
289+
owner: Input_Person!
290+
}
291+
```
292+
</div>
293+
<br/>
294+
295+
## Default Values Must be Coercible
296+
Any default value declared for an input field must be coercible by its target graph type in the target schema.
297+
298+
### Enum Values
200299

201-
public class DonutCollection
300+
Take a look at this example of an enum and input object:
301+
302+
```csharp
303+
public class Donut
202304
{
203-
public List<Donut> Donuts { get; set; }
305+
public string Name{ get; set; }
306+
public DonutFlavor Flavor { get; set; }
204307
}
205308

309+
public enum DonutFlavor
310+
{
311+
[GraphSkip]
312+
Vanilla = 0,
313+
Chocolate = 1,
314+
315+
}
206316
```
317+
318+
When `Donut` is instantiated the value of Flavor will be `Vanilla` because
319+
thats the default value (0) of the enum's underlying data type (int). However, the enum value `Vanilla` is marked as being skipped in the schema.
320+
321+
Because of this mismatch, a `GraphTypeDeclarationException` will be thrown when the introspection data for your schema is built. As a result, the server will fail to start until the problem is corrected.
322+
323+
> Enum values used for the default value of input object properties MUST also exist as values in the schema or an exception will be thrown.

website/pages/en/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class HomeSplash extends React.Component {
3131
<h2 className="projectTitle">
3232
{<span>GraphQL ASP.NET</span>}
3333
{/*<small>{siteConfig.tagline}</small>*/}
34-
<small>v0.11.0-beta</small>
34+
<small>v0.12.0-beta</small>
3535
</h2>
3636
);
3737

0 commit comments

Comments
 (0)