Skip to content

Commit 89e750a

Browse files
committed
updated union doc with new MapType verbiage
1 parent 4638e5d commit 89e750a

File tree

1 file changed

+24
-23
lines changed

1 file changed

+24
-23
lines changed

docs/types/unions.md

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ title: Unions
44
sidebar_label: Unions
55
---
66

7-
## Unions
8-
97
Unions are an aggregate graph type representing multiple, different `OBJECT` types with no guaranteed fields or interfaces in common; for instance, `Salad` or `Bread`. Because of this, unions define no fields themselves but provide a common way to query the fields of the union members when one is encountered.
108

11-
Unlike other graph types there is no concrete representation of unions. Where a `class` is an object graph type or a .NET enum is an enum graph type there is no analog for unions. Instead unions are virtual types that exist at runtime based their declaration site in a `GraphController`.
9+
Unlike other graph types there is no concrete representation of unions. Where a `class` is an object graph type or a .NET `enum` is an enum graph type there is no analog for unions. Instead unions are semi-virtual types that are created from proxy classes that represent them at design time.
1210

1311
## Declaring a Union
1412

@@ -56,17 +54,17 @@ query {
5654

5755
In this example we :
5856

59-
- Declared an action method named `RetrieveFood` with a field name of `searchFood`
60-
- Declared a union on our graph named `SaladOrBread`
61-
- Included two object types in the union: `typeof(Salad)` and `typeof(Bread)`
57+
- Declared an action method named `RetrieveFood` with a graph field name of `searchFood`
58+
- Declared a union type on our graph named `SaladOrBread`
59+
- Included two object types in the union: `Salad` and `Bread`
6260

6361
Unlike with [interfaces](./interfaces) where the possible types returned from an action method can be declared else where, you MUST provide the types to include in the union in the declaration.
6462

6563
> Union member types must be declared as part of the union.
6664
6765
### What to Return for a Union
6866

69-
Notice we have a big question mark on what the action method returns in the above example. From a C# perspective, there is no `IFood` interface shared between `Salad` and `Bread`. This represents a problem for static-typed languages like C#. Since unions are virtual types there exists no common `System.Type` that you can return for generated data. `System.Object` might work but it tends to be too general and the runtime will reject it as a safe guard.
67+
Notice we have a big question mark on what the action method returns in the above example. From a C# perspective, in this example, there is no `IFood` interface shared between `Salad` and `Bread`. This represents a problem for static-typed languages like C#. Since unions are virtual types there exists no common type that you can return for generated data. `System.Object` might work but it tends to be too general and the runtime will reject it as a safe guard.
7068

7169
So what do you do? Return an `IGraphActionResult` instead and let the runtime handle the details.
7270

@@ -92,11 +90,11 @@ public class KitchenController : GraphController
9290
}
9391
```
9492

95-
Under the hood, GraphQL ASP.NET looks at the returned object type at runtime to evaluate the graph type reference then continues on in that scope with the returned value.
93+
> Any controller action that declares a union MUST return an `IGraphActionResult`
9694
9795
## Union Proxies
9896

99-
If you need to reuse your unions in multiple methods you'll want to create a class that implements `IGraphUnionProxy` (or inherits from `GraphUnionProxy`) to encapsulate the details, then add that as a reference in your controller methods instead of the individual types. This can also be handy for uncluttering your code if you have a lot of possible types for the union. The return type of your method will still need to be `IGraphActionResult`. You cannot return a `IGraphUnionProxy` as a value.
97+
In the example above we declare the union inline. But what if we wanted to reuse the `SaladOrBread` union in multiple places. You could declare the union exactly the same on each method or use a union proxy. Create a class that implements `IGraphUnionProxy` or inherits from `GraphUnionProxy` to encapsulate the details, then add that as a reference in your controller methods instead of the individual types. This can also be handy for uncluttering your code if you have a lot of possible types for the union. The return type of your method will still need to be `IGraphActionResult`. You cannot return a `IGraphUnionProxy` as a value.
10098

10199
```csharp
102100
public class KitchenController : GraphController
@@ -107,24 +105,27 @@ public class KitchenController : GraphController
107105
}
108106

109107
// SaladOrBread.cs
110-
using GraphQL.AspNet.Schemas.TypeSystem;
111108
public class SaladOrBread : GraphUnionProxy
112109
{
113110
public SaladOrBread()
114-
: base(typeof(Salad), typeof(Bread))
115-
{}
111+
: base()
112+
{
113+
this.Name = "SaladOrBread";
114+
this.AddType(typeof(Salad));
115+
this.AddType(typeof(Bread));
116+
}
116117
}
117118
```
118119

119-
> You can create a union proxy by inheriting from `GraphUnionProxy` or directly implementing `IGraphUnionProxy`
120+
> If you don't supply a name, graphql will automatically use the name of the proxy as the name of the union.
120121
121122
## Union Name Uniqueness
122123

123-
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 types match, then there is no issue. Otherwise, a `GraphTypeDeclarationException` will be thrown.
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.
124125

125126
## Liskov Substitutions
126127

127-
[Liskov substitutions](https://en.wikipedia.org/wiki/Liskov_substitution_principle) (the L in [SOLID](https://en.wikipedia.org/wiki/SOLID)) are an important part of object oriented programming and .NET. To be able to have one class masquerade as another allows us to easily extend our code's capabilities without any rework.
128+
[Liskov substitutions](https://en.wikipedia.org/wiki/Liskov_substitution_principle) (the L in [SOLID](https://en.wikipedia.org/wiki/SOLID)) are an important part of object oriented programming. To be able to have one class masquerade as another allows us to easily extend our code's capabilities without any rework.
128129

129130

130131
```csharp
@@ -188,15 +189,12 @@ query {
188189
</div>
189190
<br/>
190191

191-
Most of the time, GraphQL ASP.NET can correctly interpret which type it should match on to allow the query to progress. However, in the above example, we declare a union, `RollOrBread`, that is of types `Roll` and `Bread` yet we return a `Bagel` from the action method.
192-
193-
Since `Bagel` inherits from `Roll` and subsequently from `Bread` which type should we match against when executing the following query?
192+
Most of the time, graphql can correctly interpret the correct union type of a returned data object and continue processing the query. However, in the above example, we declare a union, `RollOrBread`, that is of types `Roll` and `Bread` yet we return a `Bagel` from the action method.
194193

195-
The bagel is both the type `Roll` AND the type `Bread`, it could be used as either. GraphQL ASP.NET will be unable to determine which type to use and can't advance the query to select the appropriate fields. The query result is said to be indeterminate.
194+
Since `Bagel` is both a `Roll` and `Bread` which type should graphql match against to continue executing the query? Since it could be either, graphql will be unable to determine which type to use and can't advance the query to select the appropriate fields. The query result is said to be indeterminate.
196195

197-
GraphQL ASP.NET offers a way to allow you to take control of your unions and make the determination on your own. The `ResolveType` method of `IGraphUnionProxy` will be called whenever a query result is indeterminate, allowing you to choose which of your UNION's allowed types should be used.
196+
GraphQL ASP.NET offers a way to allow you to take control of your unions and make the determination on your own. The `MapType` method of `GraphUnionProxy` will be called whenever a query result is indeterminate, allowing you to choose which of your UNION's allowed types should be used.
198197

199-
> Note: `IGraphUnionProxy.ResolveType` is not based on the explicit value being inspected, but only on the `System.Type`. The results for a given field are cached for speedier type resolution on subsequent queries.
200198

201199
```csharp
202200
// RollOrBread.cs
@@ -206,7 +204,7 @@ public class RollOrBread : GraphUnionProxy
206204
: base(typeof(Roll), typeof(Bread))
207205
{}
208206

209-
public override Type ResolveType(Type runtimeObjectType)
207+
public override Type MapType(Type runtimeObjectType)
210208
{
211209
if (runtimeObjectType == typeof(Bagel))
212210
return typeof(Roll);
@@ -226,8 +224,11 @@ public class BakeryController : GraphController
226224
}
227225
```
228226

227+
> 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.
229+
229230
The query will now interpret all `Bagels` as `Rolls` and be able to process the query correctly.
230231

231232
If, via your logic you are unable to determine which of your Union's types to return then return null and GraphQL will supply the caller with an appropriate error message stating the query was indeterminate. Also, returning any type other than one that was formally declared as part of your Union will result in the same indeterminate state.
232233

233-
**Note:** Most of the time GraphQL ASP.NET will never call the `ResolveType` method on your UNION. If your union types do not share an inheritance chain, for instance, the method will never be called. If your types do share an inheritance chain, such as in the example above, considering using an interface graph type along with specific fragments instead of a UNION, to avoid the issue altogether.
234+
**Note:** Most of the time GraphQL ASP.NET will never call the `TypeMapper` on your UNION. If your union types do not share an inheritance chain, for instance, the method will never be called. If your types do share an inheritance chain, such as in the example above, considering using an interface graph type along with specific fragments instead of a UNION, to avoid the issue altogether.

0 commit comments

Comments
 (0)