Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions docs/advanced/graph-action-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class BakeryController : Controller

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

Common Action Results for MVC:
Some common ASP.NET MVC action results:

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

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.

## Common Graph Action Results
## Common Action Results

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

Expand All @@ -62,9 +62,13 @@ Built in Controller Action Methods:
- `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.
- `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.

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

- `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.
- `this.Ok()`: Indicates that the directive completed its expected operation successfully and query processing can continue.
- `this.Cancel()`: Indicates that the directive did NOT complete its operation successfully.
- If this is a type system directive, the schema will fail to complete and the server will not start.
- If this is an execution directive, the query will be abandoned and the user will receive an error message.

## Custom Graph Action Results

Expand Down
55 changes: 51 additions & 4 deletions docs/controllers/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ query {
</div>
<br/>

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

<div class="sideBySideCode hljs">
<div>
Expand Down Expand Up @@ -491,6 +491,53 @@ query {
</div>
</div>

### Working With Lists

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.

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

```csharp
public class BakeryController : GraphController
{
// a list of donuts
// schema syntax: [Donut]
[Mutation("createDonuts")]
public bool CreateDonuts(IEnumerable<Donut> donuts)
{/*....*/}

// when used as a "list of list"
// schema syntax: [[Donut]]
[Mutation("createDonutsBySet")]
public bool CreateDonuts(List<List<Donut>> donuts)
{/*....*/}

// when supplied as a regular array
// schema syntax: [Donut]
[Mutation("donutsAsAnArray")]
public bool DonutsAsAnArray(Donut[] donuts)
{/*....*/}

// This is a valid nested list
// schema syntax: [[[Donut]]]
[Mutation("mixedDonuts")]
public bool MixedDonuts(List<IEnumerable<Donut[]>> donuts)
{/*....*/}

// when used as a field of another input object
[Mutation("createDonutCollection")]
public bool CreateDonuts(DonutCollection donutCollection)
{/*....*/}

}

public class DonutCollection
{
public List<Donut> Donuts { get; set; }
}

```

### Don't Use Dictionaries

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:
Expand All @@ -501,7 +548,7 @@ You might be tempted to use a dictionary as a parameter to accept arbitrary key
```csharp
public class BakeryController : GraphController
{
// ERROR, a GraphDeclarationException
// ERROR, a GraphTypeDeclarationException
// will be thrown.
[QueryRoot]
public IEnumerable<Donut>
Expand All @@ -515,7 +562,7 @@ public class BakeryController : GraphController

```javascript
query {
searchDonuts(
searchDonuts(searchParams:
name: "jelly*"
filled: true
dayOld: false){
Expand All @@ -529,7 +576,7 @@ query {
</div>
<br/>

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

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

Expand Down
203 changes: 160 additions & 43 deletions docs/types/input-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,18 @@ title: Input Objects
sidebar_label: Input Objects
---

`INPUT_OBJECT` graph types function in much the same way as [object graph types](./objects) do.
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.
`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.

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

By Default:

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

## Names
## Customized Type Names

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

<div class="sideBySideCode hljs">
<div>
Expand Down Expand Up @@ -53,6 +49,8 @@ input NewDonutModel {
</div>
<br/>

>Not the specific callout to `InputName` in the attribution.

## Use an Empty Constructor

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.
Expand All @@ -78,7 +76,7 @@ public class DonutModel
}
```

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

## Properties Must Have a Public Setter

Expand Down Expand Up @@ -128,7 +126,8 @@ While its possible to have methods be exposed as resolvable fields on regular `O
public class Donut
{
[GraphField("salesTax")]
public decimal CalculateSalesTax(decimal taxPercentage)
public decimal CalculateSalesTax(
decimal taxPercentage)
{
return this.Price * taxPercentage;
}
Expand Down Expand Up @@ -158,49 +157,167 @@ input Input_Donut {
</div>
<br/>

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

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

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

```csharp
public class BakeryController : GraphController
// Donut.cs
public class Donut
{
// a list of donuts
// schema syntax: [Donut]
[Mutation("createDonuts")]
public bool CreateDonuts(IEnumerable<Donut> donuts)
{/*....*/}
public Donut()
{
// set custom defaults if needed
this.Type = DonutType.Vanilla;
this.Price = 2.99;
this.IsAvailable = true;
}

// when used as a "list of list"
// schema syntax: [[Donut]]
[Mutation("createDonutsBySet")]
public bool CreateDonuts(List<List<Donut>> donuts)
{/*....*/}
[Required]
public string Name { get; set; }

// when supplied as a regular array
// schema syntax: [Donut]
[Mutation("donutsAsAnArray")]
public bool DonutsAsAnArray(Donut[] donuts)
{/*....*/}
public int Id { get; set; }
public DonutType Type { get; set; }
public Bakery Bakery { get;set; }
public decimal Price { get; set; }
public decimal IsAvailable { get; set; }
}
```

// This is a valid nested list
// schema syntax: [[[Donut]]]
[Mutation("mixedDonuts")]
public bool MixedDonuts(List<IEnumerable<Donut[]>> donuts)
{/*....*/}
</div>
<div>

// when used as a field of another input object
[Mutation("createDonutCollection")]
public bool CreateDonuts(DonutCollection donutCollection)
{/*....*/}
```ruby
# GraphQL Type Definition
input Input_Donut {
name: String!
id: Int! = 0
type: DonutType! = VANILLA
bakery: Input_Bakery = null
price: Decimal! = 2.99
isAvailable: Boolean! = true
}
```

</div>
</div>
<br/>

## Non-Nullability
By default, all properties that are reference types (i.e. classes) are nullable and all value types (primatives, structs etc.) are non-nullable


<div class="hljs">

```csharp
// Donut.cs
public class Bakery
{
// a reference to another object
public Person Owner { get; set; }
}
```

```ruby
# GraphQL Type Definition
input Input_Donut {
owner: Input_Person = null
}
```

</div>
<br/>

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.


<div class="hljs">

```csharp
// Donut.cs
public class Bakery
{
public Bakery()
{
this.Owner = new Person("Bob Smith");
}

// a reference to another object
[GraphField(TypeExpression = TypeExpressions.IsNotNull)]
public Person Owner { get; set; }
}
```
```ruby
# GraphQL Type Definition
input Input_Donut {
owner: Input_Person! = { name: "Bob Smith" }
}
```
</div>
<br/>


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


Add the [Required] attribute to force a user to supply a non-null value for the field on a query document.


<div class="hljs">

```csharp
// Donut.cs
public class Bakery
{
public Bakery()
{
}

// a reference to another object
[Required]
[[GraphField(TypeExpression = TypeExpressions.IsNotNull)]]
public Person Owner { get; set; }
}
```
```ruby
# GraphQL Type Definition
input Input_Donut {
owner: Input_Person!
}
```
</div>
<br/>

## Default Values Must be Coercible
Any default value declared for an input field must be coercible by its target graph type in the target schema.

### Enum Values

public class DonutCollection
Take a look at this example of an enum and input object:

```csharp
public class Donut
{
public List<Donut> Donuts { get; set; }
public string Name{ get; set; }
public DonutFlavor Flavor { get; set; }
}

public enum DonutFlavor
{
[GraphSkip]
Vanilla = 0,
Chocolate = 1,

}
```

When `Donut` is instantiated the value of Flavor will be `Vanilla` because
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.

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.

> 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.
2 changes: 1 addition & 1 deletion website/pages/en/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class HomeSplash extends React.Component {
<h2 className="projectTitle">
{<span>GraphQL ASP.NET</span>}
{/*<small>{siteConfig.tagline}</small>*/}
<small>v0.11.0-beta</small>
<small>v0.12.0-beta</small>
</h2>
);

Expand Down