You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/advanced/custom-scalars.md
+27-25Lines changed: 27 additions & 25 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ Scalars are the most basic, fundamental unit of content in GraphQL. It is one of
8
8
9
9
Enums, being a type of their own, are very straight forward in .NET. Scalars, however; can be anything. For instance, the `Uri` scalar is represented in GraphQL by a string. On the server though, we convert it into a `System.Uri` object, with all the extra features that go along with it.
10
10
11
-
This can be done for any value that can be represented as a simple string of characters. When you create a scalar you declare its .NET type, provide a resolver that accepts raw data from a query (a `ReadOnlySpan<char>`) and returns the completed scalar value.
11
+
This can be done for any value that can be represented as a simple set of characters. When you create a scalar you declare its .NET type, provide a value resolver that accepts raw data from a query (a `ReadOnlySpan<char>`) and returns the completed scalar value.
12
12
13
13
Lets say we wanted to build a scalar called `Money` that can handle both an amount and currency symbol. We might accept it in a query like this:
14
14
@@ -58,7 +58,7 @@ query {
58
58
</div>
59
59
<br/>
60
60
61
-
The query supplies the data as a quoted string, `"$18.45"`, but our action method receives a `Money` object. Internally, GraphQL senses that the value should be `Money` from the schema definition and invokes the correct graph type to resolve the value and generate the .NET object that can be passed to our action method.
61
+
The query supplies the data as a quoted string, `"$18.45"`, but our action method receives a `Money` object. Internally, GraphQL senses that the value should be `Money` from the schema definition and invokes the correct resolver to parse the value and generate the .NET object that can be passed to our action method.
-`ValueType`: A set of flags indicating what type of source data, read from a query, this scalar is capable of processing (string, number or boolean). GraphQL will do a preemptive check and if the query document does not supply the data in the correct format it will not attempt to resolve the scalar. Most custom scalars will use `ScalarValueType.String`.
106
106
-`ObjectType`: The primary, internal type representing the scalar in .NET. In our example above we would set this to `typeof(Money)`.
107
107
-`OtherKnownTypes`: A collection of other potential types that could be used to represent the scalar in a controller class. For instance, integers can be expressed as `int` or `int?`. Most scalars will provide an empty list (e.g. `TypeCollection.Empty`).
108
-
-`SourceResolver`: An object that implements `IScalarValueResolver` that converts raw input data into the scalar's primary `ObjectType`.
109
-
-`Serializer`: An object that implements `IScalarValueSerializer` that converts the internal representation of the scalar (a class or struct) to a valid graphql scalar (a number, string or boolean).
108
+
-`SourceResolver`: An object that implements `ILeafValueResolver` which can convert raw input data into the scalar's primary `ObjectType`.
109
+
-`Serializer`: An object that implements `IScalarValueSerializer` that converts the internal representation of the scalar (a class or struct) to a valid, serialized output (a number, string or boolean).
110
110
-`ValidateObject(object)`: A method used when validating data returned from a a field resolver. GraphQL will call this method and provide the value from the resolver to determine if its acceptable and should continue resolving child fields.
111
111
112
112
> `ValidateObject(object)` should not attempt to enforce nullability rules. In general, all scalars should return `true` if the provided object is `null`.
113
113
114
-
### IScalarValueResolver Members
114
+
### ILeafValueResolver Members
115
115
116
116
-`Resolve(ReadOnlySpan<char>)`: A resolver function capable of converting an array of characters into the internal representation of the type.
117
117
118
118
#### Dealing with Escaped Strings
119
119
120
-
The span provided to `IScalarValueResolver.Resolve` will be the raw data read from the query document. If the data represents a string, it will be provided in its delimited format. This means being surrounded by quotes as well as containing escaped characters (including unicode characters):
120
+
The span provided to `ILeafValueResolver.Resolve` will be the raw data read from the query document. If the data represents a string, it will be provided in its delimited format. This means being surrounded by quotes as well as containing escaped characters (including escaped unicode characters):
121
121
122
122
Example string data:
123
123
124
124
-`"quoted string"`
125
125
-`"""triple quoted string"""`
126
126
-`"With \"\u03A3scaped ch\u03B1racters\""`;
127
127
128
-
The `StringScalarType` provides a handy static method for converting the data if you don't need to do anything special with it, `StringScalarType.UnescapeAndTrimDelimiters`.
128
+
The `StringScalarType` provides a handy static method for unescaping the data if you don't need to do anything special with it, `StringScalarType.UnescapeAndTrimDelimiters`.
129
129
130
130
Calling `UnescapeAndTrimDelimiters` with the previous examples produces:
131
131
@@ -141,17 +141,17 @@ If you throw `UnresolvedValueException` your error message will be delivered ver
141
141
142
142
### IScalarValueSerializer Members
143
143
144
-
-`Serialize(object)`: A serializer that converts the internal representation of the scalar to a [graphql compliant scalar value](https://graphql.github.io/graphql-spec/June2018/#sec-Scalars); a `number`, `string`, `bool` or `null`.
144
+
-`Serialize(object)`: A serializer that converts the internal representation of the scalar to a [graphql compliant scalar value](https://graphql.github.io/graphql-spec/October2021/#sec-Scalars); a `number`, `string`, `bool` or `null`.
145
145
- When converting to a number this can be any number value type (int, float, decimal etc.).
146
146
147
-
> `Serialize(object)` must return a valid graphql scalar type.
147
+
> `Serialize(object)` must return a string, any primative number or a boolean.
148
148
149
149
Taking a look at the at the serializer for the `Guid` scalar type we can see that while internally the `System.Guid` struct represents the value we convert it to a string when serializing it. Most scalar implementations will serialize to a string.
@@ -242,22 +245,21 @@ public void ConfigureServices(IServiceCollection services)
242
245
243
246
Since our scalar is, internally, represented by a class, if we don't pre-register it GraphQL will attempt to parse the `Money` class as an object graph type. Once registered as a scalar, any attempt to use `Money` as an object graph type will cause an exception.
244
247
245
-
## When Developing a Scalar
248
+
## Tips When Developing a Scalar
246
249
247
250
A few points about designing your scalar:
248
251
249
252
- Scalar types are expected to be thread safe.
253
+
- The runtime will pass a new instance of your scalar graph type to each registered schema. It must be declared with a public, parameterless constructor.
250
254
- Scalar types should be simple and work in isolation.
251
255
- The `ReadOnlySpan<char>` provided to the `Resolve()` method should be all the data needed to generate a value, there should be no need to perform side effects or fetch additional data.
252
-
- If you have a lot of logic to unpack a string to create your scalar consider using a regular object graph type instead.
253
-
- A scalar type is a singleton instance.
254
-
- Scalar types exist for the lifetime of the server instance.
255
-
- Scalar types should not track any state or depend on any stateful objects.
256
-
-`IScalarValueResolver.Resolve` must be **FAST**! Since your resolver is used to construct an initial query plan, it'll be called orders of magnitude more often than any controller action method.
256
+
- If you have a lot of logic to unpack a string, consider using a regular OBJECT graph type instead.
257
+
- Scalar types should not track any state or depend on any stateful objects.
258
+
-`ILeafValueResolver.Resolve` must be **FAST**! Since your resolver is used to construct an initial query plan, it'll be called #orders of magnitude more often than any controller action method.
257
259
258
-
## Aim for Fewer Scalars
260
+
###Aim for Fewer Scalars
259
261
260
-
Avoid the urge to start declaring a lot of custom scalars. In fact, chances are that you'll never need to create one. In our example we could have represented our money scalar as an object graph type:
262
+
Avoid the urge to start declaring a lot of custom scalars. In fact, chances are that you'll never need to create one. In our example we could have represented our money scalar as an INPUT_OBJECT graph type:
0 commit comments