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
2 changes: 1 addition & 1 deletion .github/workflows/deploy-website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 22
cache: yarn

- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 22
cache: yarn

- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/custom-scalars.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: custom-scalars
title: Custom Scalars
sidebar_label: Custom Scalars
sidebar_position: 3
sidebar_position: 4
---

Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being [enums](../types/enums)).
Expand Down
77 changes: 41 additions & 36 deletions docs/advanced/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public class MyInvalidDirective : GraphDirective

Execution Directives are applied to query documents and executed only on the request in which they are encountered.

### Example: @include
### Custom Example: @include

This is the code for the built in `@include` directive:

Expand Down Expand Up @@ -228,9 +228,46 @@ Batch extensions work differently than standard field resolvers; they don't reso

Type System directives are applied to schema items and executed at start up while the schema is being created.

### Example: @toLower
### @oneOf

This directive will extend the resolver of a field, as its declared **in the schema**, to turn any strings into lower case letters.
*See [input unions](input-unions.md) for further details*

### @deprecated

The `@deprecated` directive is a built in type system directive provided by graphql to indicate deprecation on a field definition or enum value. Below is the code for its implementation.

```csharp
public sealed class DeprecatedDirective : GraphDirective
{
// additional validation checks and locations are excluded for brevity
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION)]
public IGraphActionResult Execute([FromGraphQL("reason", TypeExpression = "Type!")] string reason)
{
if (this.DirectiveTarget is IDeprecatable deprecatable)
{
deprecatable.IsDeprecated = true;
deprecatable.DeprecationReason = reason;
}

return this.Ok();
}
}
```

This Directive:

- Marks a field, input field, enum value or argument as deprecated and attaches the provided deprecation reason
- Requires that a reason for deprecation be supplied
- The directive is executed once per definition its applied to when the schema is created.


:::info
Deprecation reason became a required value with the Sept. '25 specification update. (Library version v1.5)
:::

### Custom Example: @toLower

This custom example directive will extend the resolver of a field, as its declared **in the schema**, to turn any strings into lower case letters.

```csharp title="Example: ToLowerDirective.cs"
public class ToLowerDirective : GraphDirective
Expand Down Expand Up @@ -275,38 +312,6 @@ This Directive:
Notice the difference in this type system directive vs. the `@toUpper` execution directive above. Where as toUpper was declared as a PostResolver on the document part, this directive extends the primary resolver of an `IGraphField` and affects ALL queries that request this field.
:::

### Example: @deprecated

The `@deprecated` directive is a built in type system directive provided by graphql to indicate deprecation on a field definition or enum value. Below is the code for its implementation.

```csharp
public sealed class DeprecatedDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION | DirectiveLocation.ENUM_VALUE)]
public IGraphActionResult Execute([FromGraphQL("reason")] string reason = "No longer supported")
{
if (this.DirectiveTarget is IGraphField field)
{
field.IsDeprecated = true;
field.DeprecationReason = reason;
}
else if (this.DirectiveTarget is IEnumValue enumValue)
{
enumValue.IsDeprecated = true;
enumValue.DeprecationReason = reason;
}

return this.Ok();
}
}
```

This Directive:

- Targets a FIELD_DEFINITION or ENUM_VALUE.
- Marks the field or enum value as deprecated and attaches the provided deprecation reason
- The directive is executed once per field definition and enum value its applied to when the schema is created.

### Applying Type System Directives

#### Using the `[ApplyDirective]` attribute
Expand Down Expand Up @@ -507,4 +512,4 @@ public sealed class UnRedactDirective : GraphDirective
## Demo Project

See the [Demo Projects](../reference/demo-projects.md) page for a demonstration on creating a type system directive for extending a field resolver and an execution directives
that manipulates a string field result at runtime.
that manipulates a string field result at runtime.
2 changes: 1 addition & 1 deletion docs/advanced/graph-action-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: graph-action-results
title: Action Results
sidebar_label: Action Results
sidebar_position: 4
sidebar_position: 5
---

## What is an Action Result?
Expand Down
174 changes: 174 additions & 0 deletions docs/advanced/input-unions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
id: input-unions
title: Input Unions
sidebar_label: Input Unions
sidebar_position: 3
---



Input unions (Spec § [3.10.1](https://spec.graphql.org/September2025/#sec-OneOf-Input-Objects)) are a variant on standard input objects such that only one of the defined fields may be used in a query. This can be helpful if you have an input object, such as search parameters, that gives multiple ways to search, but you want the user submitting a query to choose exactly one option to search by.

By definition an input union is a standard [input object](../types/input-objects.md) (a class or a struct), all rules of standard input objects apply (i.e. field names must be unique, no use of interfaces etc.). However...

**Input unions:**<br/>
✅ MUST have no default values declared on any field.<br/>
✅ MUST have all nullable fields

:::warning
A declaration exception will be thrown and the server will fail to start if either of these rules are violated for any declared input union.
:::

## Creating An Input Union
An object can be declared as an input union in multiple ways:

### Using Attribution

Use the `[OneOf]` attribute to mark an object as ALWAYS being an input union. Any place the class or struct is referenced as an input to a method it will be handled as an input union.

```csharp title="Declaring an input union with the [OneOf] attribute"
// highlight-next-line
[OneOf]
[GraphType(InputName = "SearchOptions")]
public class SearchDonutParams
{
public string Name {get; set;}
public Flavor? Flavor {get; set; } // assume flavor is an enum
}
```

```csharp title="Using an Input Union in a Controller"
public class BakeryController : GraphController
{
[QueryRoot("findDonuts")]
// highlight-next-line
public List<Donut> FindDonuts(SearchDonutParams search)
{
/// guaranteed that only one value (name or flavor) is non-null
return [];
}
}
```

```graphql title="Equivilant schema type definition"
## relevant graphql type generated
input SearchOptions @oneOf {
name: String
flavor: Flavor
}
```

### Inherit from GraphInputUnion

Creating a class that inherits from `GraphInputUnion` works in the same way as using `[OneOf]` but adds some additional quality of life features in terms of metadata and default value handling.

_See below for details on using `GraphInputUnion`_


```csharp title="Inheriting from GraphInputUnion"
[GraphType(InputName "SearchParams")]
// highlight-next-line
public class SearchDonutParams : GraphInputUnion
{
public string Name {get; set;}
public Flavor? Flavor {get; set; } // assume flavor is an enum
}
```

```csharp title="Using an Input Union in a Controller"
public class BakeryController : GraphController
{
[QueryRoot("findDonuts")]
// highlight-next-line
public List<Donut> FindDonuts(SearchDonutParams search)
{
/// guaranteed that only one value (name or flavor) is non-null
return [];
}
}
```

```graphql title="Equivilant schema type definition"
## relevant graphql type generated
input SearchOptions @oneOf {
name: String
flavor: Flavor
}
```

## Nullable Fields
The specification defines an input union as *"a special variant of Input Object where exactly one field must be set and non-null, all others being omitted."* (Spec § [3.10.1](https://spec.graphql.org/September2025/#sec-OneOf-Input-Objects)). As such, all properties declared on a class or struct that is being used as an input union must be nullable, the supplied query MUST set exactly one field to a non-null value on a query document.

```csharp title="Example Scenarios"

// 🧨 FAIL: Flavor is non-nullable. A graph declaration exception will be thrown at start up.
[OneOf]
public class SearchDonutParams
{
public string Name {get; set;}
public Flavor Flavor {get; set; } // assume flavor is an enum
}

// 🧨 FAIL: Name declares a default value. A graph declaration exception will be thrown at start up.
[OneOf]
public class SearchDonutParams
{
public SearchDonutParams
{
this.Name = "%";
}

public string Name {get; set;}
public Flavor? Flavor {get; set; } // assume flavor is an enum
}

// ✅ SUCCESS
[OneOf]
public class SearchDonutParams
{
public string Name {get; set;}
public Flavor? Flavor {get; set; }
}
```

## Using `GraphInputUnion`
This special base type can be used to expose some additional, quality of life methods for dealing with nullability and default values.

```csharp
public abstract class GraphInputUnion
{
// Will return the value, if it was supplied on the query, otherwise fallbackValue.
// this method is is heavily optimized to be performant at runtime
public TReturn ValueOrDefault<TValue, TReturn>(Expression<Func<TObject, TValue>> selector, TReturn fallbackValue = default);
}

[GraphType(InputName = "SearchParams")]
public class SearchDonutParams : GraphInputUnion
{
public string Name {get; set;}

public Flavor? Flavor {get; set; } // assume flavor is an enum
}


// Sample Usage
public class BakeryController : GraphController
{
[QueryRoot("findDonuts")]
public List<Donut> FindDonuts(SearchDonutParams search)
{
InternalSearchParams internalParams = new();
internalParams.Name = search.ValueOrDefault(x => x.Name, "%");
internalParams.Flavor = search.ValueOrDefault(x => x.Flavor, Flavor.All);
return _service.SearchDonuts(internalParams);
}
}
```

:::info
The `ValueOrDefault()` method will return a type of the fallback value, NOT of the input object property. This allows you to return non-null defaults in place of nullable values that must be passed on the input object. This should greatly reduce bloat in transferring query supplied values and reasonable fallbacks when necessary. When returning non-reference types, they must have compatibility between the nullable and non-nullable versions (e.g. `int` and `int?`)
:::


## Support
Input Unions are supported in *GraphQL ASP.NET version 1.6* or later
2 changes: 1 addition & 1 deletion docs/advanced/multiple-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: multi-schema-support
title: Multi-Schema Support
sidebar_label: Multi-Schema Support
sidebar_position: 5
sidebar_position: 6
---

GraphQL ASP.NET supports multiple schemas on the same server out of the box. Each schema is recognized by its concrete .NET type.
Expand Down
6 changes: 5 additions & 1 deletion docs/quick/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ Use the menus on the left to navigate through the documentation. You do not need

## Nuget & Installation

<span className="pill">.NET Standard 2.0</span> <span className="pill">.NET 8</span> <span className="pill">.NET 9</span> <br/><br/>
<span className="pill">.NET 8+</span><br/><br/>


The library is available on [nuget](https://www.nuget.org/packages/GraphQL.AspNet/) and can be added to your project via the conventional means.


```powershell title="How to Install The Library"
# Using the dotnet CLI
> dotnet add package GraphQL.AspNet
Expand All @@ -30,6 +32,8 @@ The library is available on [nuget](https://www.nuget.org/packages/GraphQL.AspNe
</span>




## Other Helpful Pages

These pages may be helpful in getting started with the library:
Expand Down
5 changes: 4 additions & 1 deletion docs/types/input-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,4 +297,7 @@ public class Donut

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

## Input Unions / @oneOf
See [Input Unions](../advanced/input-unions.md) in the advanced section for details on using the `@oneOf` directive.
12 changes: 9 additions & 3 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion

const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/palenight');
const {themes} = require('prism-react-renderer');
const lightCodeTheme = themes.github;
const darkCodeTheme = themes.vsDark;

/** @type {import('@docusaurus/types').Config} */
const config = {
Expand All @@ -11,9 +12,14 @@ const config = {
url: 'https://graphql-aspnet.github.io',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',

markdown: {
hooks: {
onBrokenMarkdownLinks: 'warn',
},
},


// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
Expand Down
Loading