diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 715b9312cdb..855598b3eff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: run: npm install -g cspell - name: run cspell - run: cspell --config ./cSpell.json "website/src/**/*.md" --no-progress --no-cache + run: cspell --config ./cspell.json "website/src/**/*.md" --no-progress --no-cache linting: name: "Markdown linting" diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3ea2f266387..c15395a6a24 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "EditorConfig.EditorConfig", "k--kato.docomment", "dbaeumer.vscode-eslint", - "josefpihrt-vscode.roslynator" + "josefpihrt-vscode.roslynator", + "streetsidesoftware.code-spell-checker" ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fecc41d79c3..462e326e879 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,11 +75,11 @@ There are other available commands too. As set up in the [.build](./.build/) dir ## How to Check the docs -We use [Markdownlint](https://github.com/DavidAnson/markdownlint) to check markdown formatting and [cSpell](https://cspell.org) for spelling. We have GitHub actions to check these for PRs, but if you'd like to check locally, you can: +We use [Markdownlint](https://github.com/DavidAnson/markdownlint) to check markdown formatting and [CSpell](https://cspell.org) for spelling. We have GitHub actions to check these for PRs, but if you'd like to check locally, you can: -- Install cSpell: `npm install -g cspell` +- Install CSpell: `npm install --global cspell` - Install the markdownlint CLI: `npm install -g markdownlint-cli`. -- For spellcheck, run `cspell --config ./cSpell.json "website/src/**/*.md" --no-progress` +- For spellcheck, run `cspell --config ./cspell.json "website/src/**/*.md" --no-progress` - For markdown linting, run `markdownlint "./website/src/**/*.md" --disable MD013` ## Code of conduct diff --git a/cSpell.json b/cSpell.json deleted file mode 100644 index 17ed560b53f..00000000000 --- a/cSpell.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "version": "0.2", - "language": "en", - "words": [ - "Blazor", - "BSON", - "Cakepop", - "chillicream", - "Contoso", - "customizability", - "dataloaders", - "discoverability", - "entityframework", - "evolvability", - "fastify", - "fetchable", - "FIPS", - "Hasura", - "hotchocolate", - "jetbrains", - "JWTs", - "Linq", - "microservices", - "middlewares", - "NodaTime", - "OWIN", - "pageable", - "protobuf", - "roadmap", - "runbooks", - "struct", - "Swashbuckle", - "traversion", - "Websockets", - "Newtonsoft", - "supergraph", - "cachable", - "fricking", - "runtimes", - "NATS", - "cypher", - "sublicensable", - "meros", - "Structs", - "reencode", - "WunderGraph", - "CCPA", - "decompile" - ], - "ignoreWords": [ - "Badurina", - "enisdenjo", - "Specwise", - "strawberryshake", - "refactorings", - "mercurius", - "Giroux", - "graphyne", - "greendonut", - "Andi", - "aspnetcore", - "bananacakepop", - "Marek", - "Kellner", - "Kydne", - "GraphiQL", - "Tengler", - "Staib", - "shoooe", - "Senn", - "Rafi", - "Snapshooter", - "relayjs", - "Rgba", - "Postgraphile", - "Normen", - "typeof", - "Ufjn", - "Wguo", - "Ymgw", - "Yqhk", - "Mihsg", - "Ufjn", - "Wguo", - "Ymgw", - "Yqhk", - "Mihsg", - "Eciou", - "cfnm", - "UHVQ", - "PKCE", - "URQL", - "unpublish", - "elete" - ], - "ignoreRegExpList": [ - "\\((.*)\\)", // Markdown links - "```[a-z]*\n[\\s\\S]*?\n```", // Markdown code blocks. h/t https://coderwall.com/p/r6b4xg/regex-to-match-github-s-markdown-code-blocks, - "\\`([^\\`].*?)\\`", // inline code blocks. h/t https://stackoverflow.com/questions/41274241/how-to-capture-inline-markdown-code-but-not-a-markdown-code-fence-with-regex - "\\", // link contents - "-- snippet:(.*)", // snippet references - "\\<\\[sample:(.*)", // another kind of snippet reference - "/^\\s*```[\\s\\S]*?^\\s*```/gm", // ignoring multi-line code blocks - "featuredVideoId:(.*)", // video hash - "videoId=\"(.*)\"" // video hash - ] -} diff --git a/cspell.json b/cspell.json new file mode 100644 index 00000000000..e2699f9a86c --- /dev/null +++ b/cspell.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "version": "0.2", + "language": "en", + "dictionaries": ["csharp", "custom"], + "dictionaryDefinitions": [ + { + "name": "custom", + "path": "./dictionary.txt" + } + ], + "ignoreRegExpList": [ + "featuredVideoId:(.*)", // video hash + "videoId=\"(.*)\"" // video hash + ] +} diff --git a/dictionary.txt b/dictionary.txt new file mode 100644 index 00000000000..9e60a994484 --- /dev/null +++ b/dictionary.txt @@ -0,0 +1,88 @@ +Andi +aspnetcore +automagically +autonumber +Badurina +bananacakepop +BCPROCKS +blazor +blazorwasm +BSON +cacheable +CCPA +chillicream +Contoso +CQRS +dataloaders +decompile +enisdenjo +entityframework +FIPS +foos +fricking +Giroux +GraphiQL +graphqlconfig +graphqlrc +graphyne +greendonut +Hasura +hotchocolate +Kellner +Kydne +Linq +Marek +matchesBrics +mercurius +meros +ncontains +nendsWith +NEWHOPE +Newtonsoft +ngte +nintersects +nlte +Noda +Normen +noverlaps +Npgsql +nstartsWith +NSwag +ntouches +nwithin +oneof +OpenIddict +OWIN +pageable +PKCE +Postgraphile +preconfigured +protobuf +quox +quux +Rafi +reencode +relayjs +Rgba +runbooks +Senn +shoooe +Skywalker +snapshooter +Specwise +srid +Staib +starwars +strawberryshake +Structs +sublicensable +supergraph +Swashbuckle +Tengler +unpublish +URQL +vsix +websockets +winget +Wunder +xunit diff --git a/website/src/blog/2018-09-02-hot-chocolate-0.4.5.md b/website/src/blog/2018-09-02-hot-chocolate-0.4.5.md index c64041f27dd..23f31f482e2 100644 --- a/website/src/blog/2018-09-02-hot-chocolate-0.4.5.md +++ b/website/src/blog/2018-09-02-hot-chocolate-0.4.5.md @@ -27,7 +27,7 @@ We now are finished with implementing the query validation rules. The following - Directives Are Defined [124](https://github.com/ChilliCream/graphql-platform/issues/124) - Values of Correct Type [120](https://github.com/ChilliCream/graphql-platform/issues/120) -We now also support the `@deprectaed` directive when using schema-first. +We now also support the `@deprecated` directive when using schema-first. Furthermore, we fixed a lot of bugs around schema-first. So, at the moment code-first is still the most viable way to create a schema,but we are working hard to get both flavours on par. diff --git a/website/src/blog/2019-02-20-schema-stitching.md b/website/src/blog/2019-02-20-schema-stitching.md index 364726b7c10..55c12363e13 100644 --- a/website/src/blog/2019-02-20-schema-stitching.md +++ b/website/src/blog/2019-02-20-schema-stitching.md @@ -925,7 +925,7 @@ type UserMutations { The stitching engine provides a lot of extension points, but if we wanted to write the stitching for one specific resolver by ourselves then we could do that by using the `IStitchingContext` which is a scoped service and can be resolved through the resolver context. ```csharp -IStitchingContext stitchingContext = context.Service(); +IStitchingContext stitchingContext = context.Service(); IRemoteQueryClient remoteQueryClient = stitchingContext.GetRemoteQueryClient("messages"); IExecutionResult result = remoteQueryClient.ExecuteAsync("{ foo { bar } }") ``` diff --git a/website/src/blog/2019-04-11-integration-tests.md b/website/src/blog/2019-04-11-integration-tests.md index f70cc707c3e..5f0fba8af7d 100644 --- a/website/src/blog/2019-04-11-integration-tests.md +++ b/website/src/blog/2019-04-11-integration-tests.md @@ -116,7 +116,7 @@ The query executor will return an execution result, depending on the type of ope An `IReadOnlyQueryResult` contains basically the result graph of the query, but asserting this could be very tiresome. -My good friend [Normen](https://github.com/nscheibe) who works at Swiss Life created a snapshot testing library that basically works like [jestjs](https://jestjs.io). We use _Snapshooter_ internally to test the Hot Chocolate core. +My good friend [Normen](https://github.com/nscheibe) who works at Swiss Life created a snapshot testing library that basically works like [Jest](https://jestjs.io). We use _Snapshooter_ internally to test the Hot Chocolate core. [Snapshooter](https://github.com/SwissLife-OSS/snapshooter) will create a snapshot at the first execution of the test. The snapshots are saved in a folder `__snapshot__` that is co-located with our test class. Every consecutive test run will be validated against that first snapshot. If the snapshots do not match the test will fail and tell us what part did not match. diff --git a/website/src/blog/2019-04-12-type-system.md b/website/src/blog/2019-04-12-type-system.md index 85c0418a668..3260ef45c34 100644 --- a/website/src/blog/2019-04-12-type-system.md +++ b/website/src/blog/2019-04-12-type-system.md @@ -48,11 +48,11 @@ With our new schema builder, we did a lot of work underneath and introduced the **For what is that useful?** -For one you can now register our new `INamingConverions` with the dependency injection and then the new `SchemaBuilder` will use your naming conventions instead of the built-in naming conventions. +For one you can now register our new `INamingConventions` with the dependency injection and then the new `SchemaBuilder` will use your naming conventions instead of the built-in naming conventions. Also, you can register our new `ITypeInspector` and override how we infer schema types from POCOs. This will allow you for instance to add support for custom attributes, so no need to pollute your API with our attributes anymore. -But fear not, you do not have to implement the whole `INamingConverions` interface for instance since you can override each part of our default implementation. +But fear not, you do not have to implement the whole `INamingConventions` interface for instance since you can override each part of our default implementation. Since, in many cases we just want to tune existing naming conventions we can inherit from the default implementation `DefaultNamingConventions` and overwrite just what we want to change. @@ -88,11 +88,11 @@ In order to register our conventions with the schema builder we can do the follo ```csharp public void ConfigureServices(IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); services.AddGraphQL(sp => Schema.Create(c => { c.RegisterServiceProvider(sp); - c.RegisterQuerType(); + c.RegisterQueryType(); })); } @@ -103,7 +103,7 @@ Or we could do it like the following with the new schema builder: ```csharp public void ConfigureServices(IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); services.AddGraphQL(sp => SchemaBuilder.New() .AddQueryType() .AddServices(sp)); diff --git a/website/src/blog/2019-06-05-hot-chocolate-9.0.0/2019-06-05-hot-chocolate-9.0.0.md b/website/src/blog/2019-06-05-hot-chocolate-9.0.0/2019-06-05-hot-chocolate-9.0.0.md index 56235280bda..74c8545153f 100644 --- a/website/src/blog/2019-06-05-hot-chocolate-9.0.0/2019-06-05-hot-chocolate-9.0.0.md +++ b/website/src/blog/2019-06-05-hot-chocolate-9.0.0/2019-06-05-hot-chocolate-9.0.0.md @@ -158,7 +158,7 @@ As we promised in the past, we are adding more documentation with every release. With this release we have published a first draft of the new documentation and will add missing parts during this week. -## Banana Cakepop +## Banana Cake Pop When we released Hot Chocolate version 8 we announced a new _Hot Chocolate Developer Tool_. Since that announcement we were heavily at work building that new tool. diff --git a/website/src/blog/2019-08-14-hot-chocolate-10.0.0/2019-08-14-hot-chocolate-10.0.0.md b/website/src/blog/2019-08-14-hot-chocolate-10.0.0/2019-08-14-hot-chocolate-10.0.0.md index 26b55caf33c..97bccbd2f81 100644 --- a/website/src/blog/2019-08-14-hot-chocolate-10.0.0/2019-08-14-hot-chocolate-10.0.0.md +++ b/website/src/blog/2019-08-14-hot-chocolate-10.0.0/2019-08-14-hot-chocolate-10.0.0.md @@ -38,7 +38,7 @@ All you have to do in your schema type is to add a `UseFiltering` to your field public class QueryType : ObjectType { - protected override void Configure(IObjetcTypeDescriptor descriptor) + protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.Field(t => t.GetPersons(default)) .Type>() @@ -53,7 +53,7 @@ Filters are easily combined with our pagination. public class QueryType : ObjectType { - protected override void Configure(IObjetcTypeDescriptor descriptor) + protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.Field(t => t.GetPersons(default)) .UsePaging() @@ -68,7 +68,7 @@ Also, it is possible to customize filters by describing what fields are filterab public class QueryType : ObjectType { - protected override void Configure(IObjetcTypeDescriptor descriptor) + protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor .Field(t => t.GetPersons(default)) @@ -104,7 +104,7 @@ And then use this filter type where you need it. public class QueryType : ObjectType { - protected override void Configure(IObjetcTypeDescriptor descriptor) + protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor .Field(t => t.GetPersons(default)) @@ -344,11 +344,11 @@ Moreover, this one will be the backbone of our new stitching layer that will bri The second thing we already started work on is a client API for .NET Core. We are currently experimenting with how we design this new piece of API. We have started a discussion around this in our slack channel and will start with some coding soon. -## Banana Cakepop +## Banana Cake Pop **Oh, didn't you forget something?** -Yes, yes originally, we had planned to release _Banana Cakepop_ alongside this version. We ran into some performance issues with the tree we originally selected when using large schemas with more than 1000 types. +Yes, yes originally, we had planned to release _Banana Cake Pop_ alongside this version. We ran into some performance issues with the tree we originally selected when using large schemas with more than 1000 types. We have now started to write the tree component ourselves which is taking some extra time. We already see that we can handle now massive schemas far beyond 1000 types without any hiccups. But we have still lots to do on the new tree. @@ -360,6 +360,6 @@ We also will add more features to our filter API and make working with databases Also, we will add more subscription provider like Kafka and EventHub. -Furthermore, we will rework our `Utf8GraphQLReader` to use `ReadOnlySequence` instead of `ReadOnlySpan` in order to make this even better work with the Pipeline API. Apart from that we will optimize the syntax tree to be able to work with raw bytes instead of strings. At the moment scalar like String, Int, Float and Enum are parsed as string representation like with the original node parser. The scalar type parses then the string into the native type. The same goes for the new UTF-8 request parser. This is unnecessary with the `Utf8Parser` and `Utf8Formater`. We will change the AST to instead have the raw bytes. The current `Value` property will still be there but only for compatibility with tools that use the current version of the AST. The new scalar types will have access to a `ReadOnlySpan` and can decide how to efficiently parse literals. +Furthermore, we will rework our `Utf8GraphQLReader` to use `ReadOnlySequence` instead of `ReadOnlySpan` in order to make this even better work with the Pipeline API. Apart from that we will optimize the syntax tree to be able to work with raw bytes instead of strings. At the moment scalar like String, Int, Float and Enum are parsed as string representation like with the original node parser. The scalar type parses then the string into the native type. The same goes for the new UTF-8 request parser. This is unnecessary with the `Utf8Parser` and `Utf8Formatter`. We will change the AST to instead have the raw bytes. The current `Value` property will still be there but only for compatibility with tools that use the current version of the AST. The new scalar types will have access to a `ReadOnlySpan` and can decide how to efficiently parse literals. If you want to get into contact with us head over to our slack channel and join our community. diff --git a/website/src/blog/2019-11-25-strawberry-shake_2.md b/website/src/blog/2019-11-25-strawberry-shake_2.md index 9c77a0489fa..363c8967aa5 100644 --- a/website/src/blog/2019-11-25-strawberry-shake_2.md +++ b/website/src/blog/2019-11-25-strawberry-shake_2.md @@ -478,7 +478,7 @@ public class ByteArrayValueSerializer throw new ArgumentException( "The specified value is of an invalid type. " + - $"{ClrType.FullName} was expeceted."); + $"{ClrType.FullName} was expected."); } public override object? Deserialize(object? serialized) @@ -495,7 +495,7 @@ public class ByteArrayValueSerializer throw new ArgumentException( "The specified value is of an invalid type. " + - $"{SerializationType.FullName} was expeceted."); + $"{SerializationType.FullName} was expected."); } } ``` diff --git a/website/src/blog/2019-12-26-hotchocolate-10.3.0.md b/website/src/blog/2019-12-26-hotchocolate-10.3.0.md index b4fdd60646e..c5209fef639 100644 --- a/website/src/blog/2019-12-26-hotchocolate-10.3.0.md +++ b/website/src/blog/2019-12-26-hotchocolate-10.3.0.md @@ -568,7 +568,7 @@ With Hot Chocolate 10.3.0 we focused on productivity features that have a minor With version 11 we will take this to a whole new level with a completely new execution engine that is much more efficient and allows for completely new features like `@defer`. -Also, version 11 will introduce new tools and libraries to the platform like _Banana Cakepop_ (preview dropping very soon), _Strawberry Shake_ or our new _Visual Studio for Windows Integration_. +Also, version 11 will introduce new tools and libraries to the platform like _Banana Cake Pop_ (preview dropping very soon), _Strawberry Shake_ or our new _Visual Studio for Windows Integration_. We have a lot more in our pipeline and are totally obsessed with GraphQL and .NET. diff --git a/website/src/blog/2020-11-18-new-filtering-api/2020-11-18-new-filtering-api.md b/website/src/blog/2020-11-18-new-filtering-api/2020-11-18-new-filtering-api.md index d0c9d75ed59..4cf93382963 100644 --- a/website/src/blog/2020-11-18-new-filtering-api/2020-11-18-new-filtering-api.md +++ b/website/src/blog/2020-11-18-new-filtering-api/2020-11-18-new-filtering-api.md @@ -126,7 +126,7 @@ You first need to add the new `HotChocolate.Data` package to the project. It is also required to register filtering on the schema builder: ```csharp -public void ConfigureServcies(IServiceCollection services) { +public void ConfigureServices(IServiceCollection services) { services.AddGraphQLServer() .AddQueryType() .AddFiltering(); @@ -247,7 +247,7 @@ public static class ObjectFieldDescriptorExtensions public class ExampleObjectType : ObjectType { protected override void Configure(IObjectTypeDescriptor descriptor){ - descriptor.Field(x => x.AvaiableForAll); + descriptor.Field(x => x.AvailableForAll); descriptor.Field(x => x.OnlyForAdmins).IsAdmin(); } } diff --git a/website/src/blog/2021-01-20-hot-chocolate-logging/2021-01-10-hot-chocolate-logging.md b/website/src/blog/2021-01-20-hot-chocolate-logging/2021-01-10-hot-chocolate-logging.md index 44b17c477fd..494a998e32f 100644 --- a/website/src/blog/2021-01-20-hot-chocolate-logging/2021-01-10-hot-chocolate-logging.md +++ b/website/src/blog/2021-01-20-hot-chocolate-logging/2021-01-10-hot-chocolate-logging.md @@ -102,7 +102,7 @@ namespace Logging } _queryTimer.Stop(); stringBuilder.AppendFormat( - $"Ellapsed time for query is {_queryTimer.Elapsed.TotalMilliseconds:0.#} milliseconds."); + $"Elapsed time for query is {_queryTimer.Elapsed.TotalMilliseconds:0.#} milliseconds."); _logger.LogInformation(stringBuilder.ToString()); } } @@ -203,7 +203,7 @@ query person($upperCase: Boolean) { } Variables upperCase :true :HotChocolate.Types.BooleanType -Ellapsed time for query is 162 milliseconds. +Elapsed time for query is 162 milliseconds. ``` Notice the execution time shows as 162 milliseconds. If you execute the query again, you'll see that drop to just 1 or 2 milliseconds as now, the query, along with it's resolvers are cached by Hot Chocolate. @@ -275,7 +275,7 @@ Just like for the `ConsoleQueryLogger` class, we need to create a similar class -It also implements `DiagosticEventListener` just like `ConsoleQueryLogger` did. It gets passed in the request context, but instead of logging to the console with the `ILogger` interface and the `ConsoleLoggerExtension`, it simply calls the MiniProfiler API directly. +It also implements `DiagnosticEventListener` just like `ConsoleQueryLogger` did. It gets passed in the request context, but instead of logging to the console with the `ILogger` interface and the `ConsoleLoggerExtension`, it simply calls the MiniProfiler API directly. I could have implemented it with the ILogger interface and that would have given a lot more flexibility to our logging, but that also would have added a lot more complexity, so for now, if you want to log to MiniProfiler, add this middleware to your GraphQL. diff --git a/website/src/blog/2021-03-31-chillicream-platform-11-1/2021-03-31-chillicream-platform-11-1.md b/website/src/blog/2021-03-31-chillicream-platform-11-1/2021-03-31-chillicream-platform-11-1.md index 71b430bd3d5..7dbb53acc33 100644 --- a/website/src/blog/2021-03-31-chillicream-platform-11-1/2021-03-31-chillicream-platform-11-1.md +++ b/website/src/blog/2021-03-31-chillicream-platform-11-1/2021-03-31-chillicream-platform-11-1.md @@ -274,7 +274,7 @@ The new collection of scalars are published in the package [HotChocolate.Types.S | Isbn | The `ISBN` scalar type is a ISBN-10 or ISBN-13 number: https:\/\/en.wikipedia.org\/wiki\/International_Standard_Book_Number. | | LocalDate | The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 character sequences yyyy-mm-dd. The scalar follows the specification defined in RFC3339. | | LocalTime | The `LocalTime` scalar type is a local time string (i.e., with no associated timezone) in 24-hr `HH:mm:ss]`. | -| MacAddress | The `MacAddess` scalar type represents a IEEE 802 48-bit Mac address, represented as UTF-8 character sequences. The scalar follows the specification defined in [RFC7042](https://tools.ietf.org/html/rfc7042#page-19). | +| MacAddress | The `MacAddress` scalar type represents a IEEE 802 48-bit Mac address, represented as UTF-8 character sequences. The scalar follows the specification defined in [RFC7042](https://tools.ietf.org/html/rfc7042#page-19). | | NegativeFloat | The `NegativeFloat` scalar type represents a double‐precision fractional value less than 0. | | NegativeInt | The `NegativeIntType` scalar type represents a signed 32-bit numeric non-fractional with a maximum of -1. | | NonEmptyString | The `NonNullString` scalar type represents non-empty textual data, represented as UTF‐8 character sequences with at least one character. | @@ -712,3 +712,5 @@ We are doing as before a community gathering where we will walk you through all [pascal]: https://twitter.com/Pascal_Senn [fred]: https://github.com/fredericbirke [documentation]: /products/strawberryshake + + diff --git a/website/src/blog/2021-05-30-entity-framework/2020-03-18-entity-framework.md b/website/src/blog/2021-05-30-entity-framework/2020-03-18-entity-framework.md index a797a32c935..b16c50ba721 100644 --- a/website/src/blog/2021-05-30-entity-framework/2020-03-18-entity-framework.md +++ b/website/src/blog/2021-05-30-entity-framework/2020-03-18-entity-framework.md @@ -415,11 +415,11 @@ Let’s test our GraphQL server. dotnet run --urls http://localhost:5000 ``` -### Testing with Banana Cakepop +### Testing with Banana Cake Pop -If you have chosen _Banana Cakepop_ to test and explore the GraphQL Schema open it now. +If you have chosen _Banana Cake Pop_ to test and explore the GraphQL Schema open it now. -_Banana Cakepop_ will open with an empty tab. In the address bar type in the URL of our GraphQL server `http://localhost:5000` and hit `enter`. +_Banana Cake Pop_ will open with an empty tab. In the address bar type in the URL of our GraphQL server `http://localhost:5000` and hit `enter`. ![Hot Chocolate](banana-cake-pop-address.png) diff --git a/website/src/blog/2021-12-14-hot-chocolate-12-4/2021-12-14-hot-chocolate-12-4.md b/website/src/blog/2021-12-14-hot-chocolate-12-4/2021-12-14-hot-chocolate-12-4.md index 0feb95ad31d..7627f80918c 100644 --- a/website/src/blog/2021-12-14-hot-chocolate-12-4/2021-12-14-hot-chocolate-12-4.md +++ b/website/src/blog/2021-12-14-hot-chocolate-12-4/2021-12-14-hot-chocolate-12-4.md @@ -468,7 +468,7 @@ We also wanted to clean up the attributes around services and allow for the same ```csharp public async Task ScheduleSessionAsync( ScheduleSessionInput input, - [Service(ServiceKind.Syncronized)] ISessionService sessionService, + [Service(ServiceKind.Synchronized)] ISessionService sessionService, [Service] ITopicEventSender eventSender) { // code omitted for brevity @@ -498,7 +498,7 @@ builder.Services The DBContext can be registered as a well-known DBContext with three different behaviors. -The first and the default is `DbContextKind.Syncronized` which will ensure that all resolvers that access such a DBContext synchronize their access through the query execution plan. +The first and the default is `DbContextKind.Synchronized` which will ensure that all resolvers that access such a DBContext synchronize their access through the query execution plan. You also can use a pooled DBContext with the `DbContextKind.Pooled`. In this case, we will wrap a middleware around your resolver that will retrieve the DBContext through the DBContextFactory, inject the DBContext in your resolver and dispose of it once the resolver pipeline is finished executing. diff --git a/website/src/blog/2023-02-08-new-in-hot-chocolate-13/2023-02-08-new-in-hot-chocolate-13.md b/website/src/blog/2023-02-08-new-in-hot-chocolate-13/2023-02-08-new-in-hot-chocolate-13.md index e2cf6eae7de..21bbc1205ee 100644 --- a/website/src/blog/2023-02-08-new-in-hot-chocolate-13/2023-02-08-new-in-hot-chocolate-13.md +++ b/website/src/blog/2023-02-08-new-in-hot-chocolate-13/2023-02-08-new-in-hot-chocolate-13.md @@ -105,7 +105,7 @@ public static class Query } ``` -The GraphQL cache-control header will collect the allowed amount of time the response is cachable and exposes this as a cache-control header which consequently can be used by CDNs or browsers to cache the result. +The GraphQL cache-control header will collect the allowed amount of time the response is cacheable and exposes this as a cache-control header which consequently can be used by CDNs or browsers to cache the result. ### Null Values diff --git a/website/src/blog/2023-08-15-fusion/2023-08-15-fusion.md b/website/src/blog/2023-08-15-fusion/2023-08-15-fusion.md index 99fec867d32..65f55a4049c 100644 --- a/website/src/blog/2023-08-15-fusion/2023-08-15-fusion.md +++ b/website/src/blog/2023-08-15-fusion/2023-08-15-fusion.md @@ -28,7 +28,7 @@ Up to this point, the GraphQL landscape has lacked an open specification tailore ## Let's share and compete -Late last year, [ChilliCream](https://chillicream.com) and [The Guild](https://the-guild.dev/) met in Paris and discussed their approaches towards distributed GraphQL. It became clear that both companies were solving similar problems, and we decided to join forces on this project. ChilliCream would provide the initial work on the Fusion spec and implementation. At the same time, The Guild would start specifying their work on [GraphQL Mesh Gateway](https://the-guild.dev/graphql/mesh) with [OpenAPI support](https://the-guild.dev/graphql/mesh/docs/handlers/openapi) and help shape the initial Fusion spec. As we started, work on prototypes and the initial spect texts, we reached out to more companies in the community to see if there was interest in collaboration. It turns out that the GraphQL community is hungry for an open specification to standardize distributed GraphQL application gateways. [Hasura](https://hasura.io/), [IBM](https://www.ibm.com), [solo.io](https://www.solo.io/), [AWS AppSync](https://aws.amazon.com/de/appsync/), [WunderGraph](https://wundergraph.com/) have all joined the effort for creating a common spec. +Late last year, [ChilliCream](https://chillicream.com) and [The Guild](https://the-guild.dev/) met in Paris and discussed their approaches towards distributed GraphQL. It became clear that both companies were solving similar problems, and we decided to join forces on this project. ChilliCream would provide the initial work on the Fusion spec and implementation. At the same time, The Guild would start specifying their work on [GraphQL Mesh Gateway](https://the-guild.dev/graphql/mesh) with [OpenAPI support](https://the-guild.dev/graphql/mesh/docs/handlers/openapi) and help shape the initial Fusion spec. As we started, work on prototypes and the initial spec texts, we reached out to more companies in the community to see if there was interest in collaboration. It turns out that the GraphQL community is hungry for an open specification to standardize distributed GraphQL application gateways. [Hasura](https://hasura.io/), [IBM](https://www.ibm.com), [solo.io](https://www.solo.io/), [AWS AppSync](https://aws.amazon.com/de/appsync/), [WunderGraph](https://wundergraph.com/) have all joined the effort for creating a common spec. Today, we are thrilled to unveil GraphQL-Fusion, an open specification under the **MIT license**. This initiative empowers everyone to craft tools and solutions centered around distributed GraphQL services. Complementing this announcement, we're also introducing [Hot Chocolate](https://chillicream.com/docs/hotchocolate) Fusion, an early implementation of the GraphQL-Fusion spec draft. @@ -582,7 +582,7 @@ We considered CI/CD from the start when conceptualizing Fusion and structured it But the core principle is that this is open and built into the GraphQL-Fusion spec. To provide tooling a single file containing all the information needed for gateway configuration and even space for gateway-specific features, we've adopted the [Open Packaging Convention](https://en.wikipedia.org/wiki/Open_Packaging_Conventions) as a container for the GraphQL-Fusion Configuration (.fgp). -The [Open Packaging Convention](https://en.wikipedia.org/wiki/Open_Packaging_Conventions) is an open standard provided by [Microsoft](https://microsoft.com), used for everything from Word Documents (.docx) to VSCode extension packages (.visx). Simply put, think of it as a ZIP container with metadata and relations between its artifacts. The GraphQL-Fusion convention contains the mandatory Fusion Graph document. This document is all that's needed to run and configure a Gateway implementing the core specification. +The [Open Packaging Convention](https://en.wikipedia.org/wiki/Open_Packaging_Conventions) is an open standard provided by [Microsoft](https://microsoft.com), used for everything from Word Documents (.docx) to VSCode extension packages (.vsix). Simply put, think of it as a ZIP container with metadata and relations between its artifacts. The GraphQL-Fusion convention contains the mandatory Fusion Graph document. This document is all that's needed to run and configure a Gateway implementing the core specification. Additionally, it contains all subgraph schema documents, the publicly exposed Gateway schema, and composition settings the user has opted into. Having all these artifacts in one place gives us a single artifact that we can pass on from the schema composition in a CI/CD pipeline to the schema registry and from there to the actual gateway. We have customers already using this with their custom solutions for distributing the configuration from their deployment pipeline to their gateway or by using our Cloud Services ([Banana Cake Pop](https://eat.bananacakepop.com)). Besides these standard artifacts included in the package, it also allows Gateway implementers to store custom configurations to specify GraphQL WAF settings and more. diff --git a/website/src/docs/bananacakepop/v2/apis/open-telemetry.md b/website/src/docs/bananacakepop/v2/apis/open-telemetry.md index 75b5136280a..ee1ed3bf2cd 100644 --- a/website/src/docs/bananacakepop/v2/apis/open-telemetry.md +++ b/website/src/docs/bananacakepop/v2/apis/open-telemetry.md @@ -103,7 +103,7 @@ The throughput graph shows you the operations per minute over time. You can see You can track how many requests are done by each client. This helps you to understand which client is impacting your system the most. To track this, your clients need to send two headers with each request: -- `GraphQL-Client-Id` - The id of the client. You can get the id from the client by execution `barsita client list` in your terminal. +- `GraphQL-Client-Id` - The id of the client. You can get the id from the client by execution `barista client list` in your terminal. - `GraphQL-Client-Version` - The version of the client ## Errors diff --git a/website/src/docs/bananacakepop/v2/bcp-services.md b/website/src/docs/bananacakepop/v2/bcp-services.md index 9a66e8abcaa..2787f91147f 100644 --- a/website/src/docs/bananacakepop/v2/bcp-services.md +++ b/website/src/docs/bananacakepop/v2/bcp-services.md @@ -44,3 +44,5 @@ app.Run(); 4. Retrieve the API id and API key from Barista using the `barista api list` and `barista api-key create` commands respectively. Instructions for these commands can be found [here](/docs/barista/v1). Congratulations! You have successfully integrated BananaCake Pop into your HotChocolate server. You can now publish new versions of your clients and your server will automatically retrieve the latest persisted queries. + + diff --git a/website/src/docs/bananacakepop/v2/getting-started.md b/website/src/docs/bananacakepop/v2/getting-started.md index a86a3f3912c..4ae92e999a8 100644 --- a/website/src/docs/bananacakepop/v2/getting-started.md +++ b/website/src/docs/bananacakepop/v2/getting-started.md @@ -93,3 +93,5 @@ Sign in to your account by clicking the 'Sign In' button on the user icon in the **Step 10:** Great job! You've successfully created, executed, and saved your first GraphQL query using Banana Cake Pop. To learn more about the Banana Cake Pop User Interface, head over to the [Explore the UI](/docs/bananacakepop/v2/explore-the-ui) guide. Happy querying! + + diff --git a/website/src/docs/barista/v1/commands/client.md b/website/src/docs/barista/v1/commands/client.md index 7112a29f815..ed1daca88e6 100644 --- a/website/src/docs/barista/v1/commands/client.md +++ b/website/src/docs/barista/v1/commands/client.md @@ -116,3 +116,5 @@ barista client show Q2xpZW50CmdiMDk4MDEyODM0MTI0MDIxNDgxMjQzMTI0MTI= **Arguments:** - ``: The ID of the client whose details you want to show. This ID can be retrieved with the `barista client list` command. + + diff --git a/website/src/docs/barista/v1/commands/schema.md b/website/src/docs/barista/v1/commands/schema.md index ac286f26c5a..a4645361cea 100644 --- a/website/src/docs/barista/v1/commands/schema.md +++ b/website/src/docs/barista/v1/commands/schema.md @@ -61,3 +61,5 @@ barista schema upload \ - `--api-id ` **(required)**: Specifies the ID of the API to which you are uploading the schema. This ID can be retrieved with the `barista api list` command. You can set it from the environment variable `BARISTA_API_ID`. - `--api-key `: Specifies the API key used for authentication. It doesn't have to be provided when you are logged in. Otherwise, it's the secret that `barista api-key create` returns. You can set it from the environment variable `BARISTA_API_KEY`. + + diff --git a/website/src/docs/barista/v1/commands/stage.md b/website/src/docs/barista/v1/commands/stage.md index b7af505bcb8..b047558713c 100644 --- a/website/src/docs/barista/v1/commands/stage.md +++ b/website/src/docs/barista/v1/commands/stage.md @@ -66,3 +66,5 @@ barista stage list --api-id QXBpCmdiOGRiYzk5NmRiNTI0OWRlYWIyM2ExNGRiYjdhMTIzNA== **Options:** - `--api-id `: Specifies the ID of the API for which you want to list the stages. This ID can be retrieved with the `barista api list` command. You can set it from the environment variable `BARISTA_API_ID`. + + diff --git a/website/src/docs/hotchocolate/v10/data-fetching/pagination.md b/website/src/docs/hotchocolate/v10/data-fetching/pagination.md index a0aeea4a28c..81e19e1f7fd 100644 --- a/website/src/docs/hotchocolate/v10/data-fetching/pagination.md +++ b/website/src/docs/hotchocolate/v10/data-fetching/pagination.md @@ -149,7 +149,7 @@ public class QueryType ? (bool)d : ctx.Argument("descending"); - // set the curosr sorting property. + // set the cursor sorting property. cursorProperties["descending"] = descending; IEnumerable strings = ctx.Parent().Strings; diff --git a/website/src/docs/hotchocolate/v10/execution-engine/batching.md b/website/src/docs/hotchocolate/v10/execution-engine/batching.md index 1b21cb83f6d..7bc94e148af 100644 --- a/website/src/docs/hotchocolate/v10/execution-engine/batching.md +++ b/website/src/docs/hotchocolate/v10/execution-engine/batching.md @@ -191,3 +191,5 @@ while (!responseStream.IsCompleted) Console.WriteLine(query.ToJson()); } ``` + + diff --git a/website/src/docs/hotchocolate/v10/execution-engine/persisted-queries.md b/website/src/docs/hotchocolate/v10/execution-engine/persisted-queries.md index ebbe3c004aa..b2ff091b33a 100644 --- a/website/src/docs/hotchocolate/v10/execution-engine/persisted-queries.md +++ b/website/src/docs/hotchocolate/v10/execution-engine/persisted-queries.md @@ -157,3 +157,5 @@ OK that’s it. > We are currently working on enabling this flow with _Relay_. Stay tuned for updates on this one. > Read more about how to do active persisted queries with [Apollo](https://medium.com/open-graphql/graphql-dynamic-persisted-queries-eb259700f1d3). + + diff --git a/website/src/docs/hotchocolate/v10/schema/code-first-object-type.md b/website/src/docs/hotchocolate/v10/schema/code-first-object-type.md index 175cc635d5b..6a9c4bed78e 100644 --- a/website/src/docs/hotchocolate/v10/schema/code-first-object-type.md +++ b/website/src/docs/hotchocolate/v10/schema/code-first-object-type.md @@ -287,3 +287,5 @@ type Bar { ``` Compared to the generic descriptor interface you are loosing the generic field descriptor that is able to bind a field to a .NET property or method. + + diff --git a/website/src/docs/hotchocolate/v10/schema/descriptions.md b/website/src/docs/hotchocolate/v10/schema/descriptions.md index 45ef925a238..aa0090a53cf 100644 --- a/website/src/docs/hotchocolate/v10/schema/descriptions.md +++ b/website/src/docs/hotchocolate/v10/schema/descriptions.md @@ -252,7 +252,7 @@ type Query { ## Fluent APIs -The `IObjecTypeDescriptor` includes fluent APIs that enable setting descriptions through a declarative syntax. You can easily access these fluent APIs by creating a class that inherits from the `ObjectType` class and overriding the `Configure(IObjectTypeDescriptor)` method. For example, given the following C# code you would have the following GraphQL schema. +The `IObjectTypeDescriptor` includes fluent APIs that enable setting descriptions through a declarative syntax. You can easily access these fluent APIs by creating a class that inherits from the `ObjectType` class and overriding the `Configure(IObjectTypeDescriptor)` method. For example, given the following C# code you would have the following GraphQL schema. ```csharp public class Droid diff --git a/website/src/docs/hotchocolate/v10/server/index.md b/website/src/docs/hotchocolate/v10/server/index.md index 623f77b832d..7b9870caea3 100644 --- a/website/src/docs/hotchocolate/v10/server/index.md +++ b/website/src/docs/hotchocolate/v10/server/index.md @@ -259,3 +259,5 @@ We have a added some helper extension to swap the default serializer out: ```csharp services.AddResponseStreamSerializer(); ``` + + diff --git a/website/src/docs/hotchocolate/v10/stitching.md b/website/src/docs/hotchocolate/v10/stitching.md index 4007ba9ad1b..c494f8bb6a6 100644 --- a/website/src/docs/hotchocolate/v10/stitching.md +++ b/website/src/docs/hotchocolate/v10/stitching.md @@ -999,7 +999,7 @@ type UserMutations { The stitching engine provides a lot of extension points, but if we wanted to write the stitching for one specific resolver by ourselves then we could do that by using the `IStitchingContext` which is a scoped service and can be resolved through the resolver context. ```csharp -IStitchingContext stitchingContext = context.Service(); +IStitchingContext stitchingContext = context.Service(); IRemoteQueryClient remoteQueryClient = stitchingContext.GetRemoteQueryClient("messages"); IExecutionResult result = remoteQueryClient.ExecuteAsync("{ foo { bar } }") ``` diff --git a/website/src/docs/hotchocolate/v11/api-reference/aspnetcore.md b/website/src/docs/hotchocolate/v11/api-reference/aspnetcore.md index 7f8adc6aa04..1d6ccea5938 100644 --- a/website/src/docs/hotchocolate/v11/api-reference/aspnetcore.md +++ b/website/src/docs/hotchocolate/v11/api-reference/aspnetcore.md @@ -542,3 +542,5 @@ A custom socket session interceptor can be registered like the following: ```csharp services.AddSocketSessionInterceptor(); ``` + + diff --git a/website/src/docs/hotchocolate/v11/api-reference/executable.md b/website/src/docs/hotchocolate/v11/api-reference/executable.md index d366ed0fb8e..4a378d98e62 100644 --- a/website/src/docs/hotchocolate/v11/api-reference/executable.md +++ b/website/src/docs/hotchocolate/v11/api-reference/executable.md @@ -13,14 +13,14 @@ public class User public string Name { get; } } -public interface IUserRepostiory +public interface IUserRepository { public IExecutable FindAll(); } public class Query { - public IExecutable GetUsers([Service] IUserRepostiory repo) => + public IExecutable GetUsers([Service] IUserRepository repo) => repo.FindAll(); } ``` diff --git a/website/src/docs/hotchocolate/v11/api-reference/extending-filtering.md b/website/src/docs/hotchocolate/v11/api-reference/extending-filtering.md index 40fd06a7a06..fe6ce07709e 100644 --- a/website/src/docs/hotchocolate/v11/api-reference/extending-filtering.md +++ b/website/src/docs/hotchocolate/v11/api-reference/extending-filtering.md @@ -156,7 +156,7 @@ public class CustomConvention : FilterConvention { protected override void Configure(IFilterConventionDescriptor descriptor) { - desciptor.AddDefaults(); + descriptor.AddDefaults(); } public override NameString GetTypeName(Type runtimeType) => @@ -253,13 +253,13 @@ You can override `TryHandleOperation` to handle operations. ## The Context -As the visitor and the field handlers are singletons, a context object is passed along with the traversion of input objects. +As the visitor and the field handlers are singletons, a context object is passed along with the traversal of input objects. Field handlers can push data on this context, to make it available for other handlers further down in the tree. The context contains `Types`, `Operations`, `Errors` and `Scopes`. It is very provider-specific what data you need to store in the context. In the case of the `IQueryable` provider, it also contains `RuntimeTypes` and knows if the source is `InMemory` or a database call. -With `Scopes` it is possible to add multiple logical layers to a context. In the case of `IQuerable` this is needed, whenever a new closure starts +With `Scopes` it is possible to add multiple logical layers to a context. In the case of `IQueryable` this is needed, whenever a new closure starts ```csharp // /------------------------ SCOPE 1 -----------------------------\ @@ -302,7 +302,7 @@ A little simplified this is what happens during visitation: # instance[0] = y # level[0] = [] any: { - # Push poperty Address.Street onto the scope + # Push property Address.Street onto the scope # instance[1] = y.Street # level[1] = [] street: { @@ -311,7 +311,7 @@ A little simplified this is what happens during visitation: # level[2] = [y.Street == "221B Baker Street"] eq: "221B Baker Street" } - # Combine everything of the current level and pop the porperty street from the instance + # Combine everything of the current level and pop the property street from the instance # instance[1] = y.Street # level[1] = [y.Street == "221B Baker Street"] } @@ -319,11 +319,11 @@ A little simplified this is what happens during visitation: # instance[2] = x.Company.Addresses # level[2] = [x.Company.Addresses.Any(y => y.Street == "221B Baker Street")] } - # Combine everything of the current level and pop the porperty street from the instance + # Combine everything of the current level and pop the property street from the instance # instance[1] = x.Company # level[1] = [x.Company.Addresses.Any(y => y.Street == "221B Baker Street")] } - # Combine everything of the current level and pop the porperty street from the instance + # Combine everything of the current level and pop the property street from the instance # instance[0] = x # level[0] = [x.Company.Addresses.Any(y => y.Street == "221B Baker Street")] } @@ -340,7 +340,7 @@ The default filtering implementation uses `IQueryable` under the hood. You can c The following example creates a `StringOperationHandler` that supports case insensitive filtering: ```csharp -// The QueryableStringOperationHandler already has an implemenation of CanHandle +// The QueryableStringOperationHandler already has an implementation of CanHandle // It checks if the field is declared in a string operation type and also checks if // the operation of this field uses the `Operation` specified in the override property further // below @@ -362,7 +362,7 @@ public class QueryableStringInvariantEqualsHandler : QueryableStringOperationHan IValueNode value, object parsedValue) { - // We get the instance of the context. This is the expression path to the propert + // We get the instance of the context. This is the expression path to the property // e.g. ~> y.Street Expression property = context.GetInstance(); @@ -370,7 +370,7 @@ public class QueryableStringInvariantEqualsHandler : QueryableStringOperationHan // e.g. ~> eq: "221B Baker Street" if (parsedValue is string str) { - // Creates and returnes the operation + // Creates and returns the operation // e.g. ~> y.Street.ToLower() == "221b baker street" return Expression.Equal( Expression.Call(property, _toLower), diff --git a/website/src/docs/hotchocolate/v11/api-reference/filtering.md b/website/src/docs/hotchocolate/v11/api-reference/filtering.md deleted file mode 100644 index 51729e0e5b5..00000000000 --- a/website/src/docs/hotchocolate/v11/api-reference/filtering.md +++ /dev/null @@ -1,2031 +0,0 @@ ---- -title: Filtering ---- - -**What are filters?** - -With Hot Chocolate filters, you can expose complex filter objects through your GraphQL API that translates to native database queries. - -The default filter implementation translates filters to expression trees and applies these on `IQueryable`. - -# Overview - -Filters by default work on `IQueryable` but you can also easily customize them to use other interfaces. - -Hot Chocolate by default will inspect your .NET model and infer the possible filter operations from it. - -The following type would yield the following filter operations: - -```csharp -public class Foo -{ - public string Bar { get; set; } -} -``` - -```graphql -input FooFilter { - bar: String - bar_contains: String - bar_ends_with: String - bar_in: [String] - bar_not: String - bar_not_contains: String - bar_not_ends_with: String - bar_not_in: [String] - bar_not_starts_with: String - bar_starts_with: String - AND: [FooFilter!] - OR: [FooFilter!] -} -``` - -**So how can we get started with filters?** - -Getting started with filters is very easy, especially if you do not want to explicitly define filters or customize anything. - -Hot Chocolate will infer the filters directly from your .Net Model and then use a Middleware to apply filters to `IQueryable` or `IEnumerable` on execution. - -> ⚠️ **Note:** If you use more than middleware, keep in mind that **ORDER MATTERS**. - -> ⚠️ **Note:** Be sure to install the `HotChocolate.Types.Filters` NuGet package. - -In the following example, the person resolver returns the `IQueryable` representing the data source. The `IQueryable` represents a not executed database query on which Hot Chocolate can apply filters. - -**Code First** - -The next thing to note is the `UseFiltering` extension method which adds the filter argument to the field and a middleware that can apply those filters to the `IQueryable`. The execution engine will, in the end, execute the `IQueryable` and fetch the data. - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPersons(default)) - .Type>>() - .UseFiltering(); - } -} - -public class Query -{ - public IQueryable GetPersons([Service]IPersonRepository repository) - { - repository.GetPersons(); - } -} -``` - -**Pure Code First** - -The field descriptor attribute `[UseFiltering]` does apply the extension method `UseFiltering()` on the field descriptor. - -```csharp -public class Query -{ - [UseFiltering] - public IQueryable GetPersons([Service]IPersonRepository repository) - { - repository.GetPersons(); - } -} -``` - -**Schema First** - -> ⚠️ **Note:** Schema first does currently not support filtering! - -# Customizing Filters - -A `FilterInputType` defines a GraphQL input type, that Hot Chocolate uses for filtering. You can customize these similar to a normal input type. You can change the name of the type; add, remove, or change operations or directive; and configure the binding behavior. To define and customize a filter we must inherit from `FilterInputType` and configure it like any other type by overriding the `Configure` method. - -```csharp -public class PersonFilterType - : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor - .BindFieldsExplicitly() - .Filter(t => t.Name) - .BindOperationsExplicitly() - .AllowEquals().Name("equals").And() - .AllowContains().Name("contains").And() - .AllowIn().Name("in"); - } -} -``` - -The above filter type defines explicitly which fields allow filtering and what operations these filters allow. Additionally, the filter type changes the name of the equals operation of the filter of the field `Name` to `equals`. - -To make use of the configuration in this filter type, you can provide it to the `UseFiltering` extension method as the generic type argument. - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPerson(default)) - .Type>>(); - .UseFiltering() - } -} -``` - -# Sorting - -Like with filter support you can add sorting support to your database queries. - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPerson(default)) - .Type>>(); - .UseSorting() - } -} -``` - -> Warning: Be sure to install the `HotChocolate.Types.Sorting` NuGet package. - -If you want to combine for instance paging, filtering, and sorting make sure that the order is like follows: - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPerson(default)) - .UsePaging() - .UseFiltering() - .UseSorting(); - } -} -``` - -**Why is order important?** - -Paging, filtering, and sorting are modular middlewares that form the field resolver pipeline. - -The above example forms the following pipeline: - -`Paging -> Filtering -> Sorting -> Field Resolver` - -The paging middleware will first delegate to the next middleware, which is filtering. - -The filtering middleware will also first delegate to the next middleware, which is sorting. - -The sorting middleware will again first delegate to the next middleware, which is the actual field resolver. - -The field resolver will call `GetPerson` which returns in this example an `IQueryable`. The queryable represents a not yet executed database query. - -After the resolver has been executed and puts its result onto the middleware context the sorting middleware will apply for the sort order on the query. - -After the sorting middleware has been executed and updated the result on the middleware context the filtering middleware will apply its filters on the queryable and updates the result on the middleware context. - -After the paging middleware has been executed and updated the result on the middleware context the paging middleware will slice the data and execute the queryable which will then actually pull in data from the data source. - -So, if we, for instance, applied paging as our last middleware the data set would have been sliced first and then filtered which in most cases is not what we actually want. - -# Filter & Operations Kinds - -You can break down filtering into different kinds of filters that then have different operations. -The filter kind is bound to the type. A string is fundamentally something different than an array or an object. -Each filter kind has different operations that you can apply to it. Some operations are unique to a filter and some operations are shared across multiple filter -e.g. A string filter has string specific operations like `Contains` or `EndsWith` but still shares the operations `Equals` and `NotEquals` with the boolean filter. - -## Filter Kinds - -Hot Chocolate knows following filter kinds - -| Kind | Operations | -| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| String | Equals, In, EndsWith, StartsWith, Contains, NotEquals, NotIn, NotEndsWith, NotStartsWith, NotContains | -| Bool | Equals, NotEquals | -| Object | Equals | -| Array | Some, Any, All, None | -| Comparable | Equals, In, GreaterThan, GreaterThanOrEqual, LowerThan, LowerThanOrEqual, NotEquals, NotIn, NotGreaterThan, NotGreaterThanOrEqual, NotLowerThan, NotLowerThanOrEqual | - -## Operations Kinds - -Hot Chocolate knows following operation kinds - -| Kind | Operations | -| ---------------------- | ----------------------------------------------------------------------------------------------------- | -| Equals | Compares the equality of input value and property value | -| NotEquals | negation of Equals | -| In | Checks if the property value is contained in a given list of input values | -| NotIn | negation of In | -| GreaterThan | checks if the input value is greater than the property value | -| NotGreaterThan | negation of GreaterThan | -| GreaterThanOrEquals | checks if the input value is greater than or equal to the property value | -| NotGreaterThanOrEquals | negation of GreaterThanOrEquals | -| LowerThan | checks if the input value is lower than the property value | -| NotLowerThan | negation of LowerThan | -| LowerThanOrEquals | checks if the input value is lower than or equal to the property value | -| NotLowerThanOrEquals | negation of LowerThanOrEquals | -| EndsWith | checks if the property value ends with the input value | -| NotEndsWith | negation of EndsWith | -| StartsWith | checks if the property value starts with the input value | -| NotStartsWith | negation of StartsWith | -| Contains | checks if the input value is contained in the property value | -| NotContains | negation of Contains | -| Some | checks if at least one element in the collection exists | -| Some | checks if at least one element of the property value meets the condition provided by the input value | -| None | checks if no element of the property value meets the condition provided by the input value | -| All | checks if all least one element of the property value meets the condition provided by the input value | - -## Boolean Filter - -In this example, we look at the filter configuration of a Boolean filter. -As an example, we will use the following model: - -```csharp -public class User -{ - public bool IsOnline {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - isOnline: Boolean -} - -input UserFilter { - isOnline: Boolean - isOnline_not: Boolean - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -### Boolean Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of Boolean filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name) - .AllowEquals().And() - .AllowNotEquals(); - } -} -``` - -## Comparable Filter - -In this example, we look at the filter configuration of a comparable filter. - -A comparable filter is generated for all values that implement IComparable except string and boolean. -e.g. `csharp±enum`, `csharp±int`, `csharp±DateTime`... - -As an example, we will use the following model: - -```csharp -public class User -{ - public int LoggingCount {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - loggingCount: Int -} - -input UserFilter { - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - loggingCount_not: Int - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -### Comparable Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of comparable filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name) - .AllowEquals().And() - .AllowNotEquals().And() - .AllowGreaterThan().And() - .AllowNotGreaterThan().And() - .AllowGreaterThanOrEqals().And() - .AllowNotGreaterThanOrEqals().And() - .AllowLowerThan().And() - .AllowNotLowerThan().And() - .AllowLowerThanOrEqals().And() - .AllowNotLowerThanOrEqals().And() - .AllowIn().And() - .AllowNotIn(); - } -} -``` - -## String Filter - -In this example, we look at the filter configuration of a String filter. -As an example, we will use the following model: - -```csharp -public class User -{ - public string Name {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - name: String -} - -input UserFilter { - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -### String Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of string filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name) - .AllowEquals().And() - .AllowNotEquals().And() - .AllowContains().And() - .AllowNotContains().And() - .AllowStartsWith().And() - .AllowNotStartsWith().And() - .AllowEndsWith().And() - .AllowNotEndsWith().And() - .AllowIn().And() - .AllowNotIn(); - } -} -``` - -## Object Filter - -In this example, we look at the filter configuration of an object filter. - -Hot Chocolate generated object filters for all objects. Since Version 11, Hot Chocolate also generates filter types for nested objects. You can also use object filters to filter over database relations. - -As an example, we will use the following model: - -```csharp -public class User -{ - public Address Address {get;set;} -} - -public class Address -{ - public string Street {get;set;} - - public bool IsPrimary {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - address: Address -} - -type Address { - isPrimary: Boolean - street: String -} - -input UserFilter { - address: AddressFilter - AND: [UserFilter!] - OR: [UserFilter!] -} - -input AddressFilter { - is_primary: Boolean - is_primary_not: Boolean - street: String - street_contains: String - street_ends_with: String - street_in: [String] - street_not: String - street_not_contains: String - street_not_ends_with: String - street_not_in: [String] - street_not_starts_with: String - street_starts_with: String - AND: [AddressFilter!] - OR: [AddressFilter!] -} -``` - -### Object Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of comparable filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Object(x => x.Address); - } -} -``` - -**Configuring a custom nested filter type:** - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Object(x => x.Address).AllowObject(); - } -} - -public class AddressFilterType : FilterInputType
-{ - protected override void Configure( - IFilterInputTypeDescriptor
descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.IsPrimary); - } -} - -// or inline - -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Object(x => x.Address) - .AllowObject( - y => y.BindFieldsExplicitly().Filter(z => z.IsPrimary)); - } -} - - -``` - -## List Filter - -In this example, we look at the filter configuration of a list filter. - -Hot Chocolate can also generate filters for `IEnumerable`s. Like object filter, Hot Chocolate generates filters for the whole object tree. List filter addresses scalars and object values differently. -In the case the field is a scalar value, Hot Chocolate creates and object type to address the different operations of this scalar. e.g. If you specify filters for a list of strings, Hot Chocolate creates an object type that contains all operations of the string filter. -In case the list holds a complex object, it generates an object filter for this object instead. - -Hot Chocolate implicitly generates filters for all properties that implement `IEnumerable`. -e.g. `csharp±string[]`, `csharp±List`, `csharp±IEnumerable`... - -As an example, we will use the following model: - -```csharp -public class User -{ - public string[] Roles {get;set;} - - public IEnumerable
Addresses {get;set;} -} - -public class Address -{ - public string Street {get;set;} - - public bool IsPrimary {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - addresses: [Address] - roles: [String] -} - -type Address { - isPrimary: Boolean - street: String -} - -input UserFilter { - addresses_some: AddressFilter - addresses_all: AddressFilter - addresses_none: AddressFilter - addresses_any: Boolean - roles_some: ISingleFilterOfStringFilter - roles_all: ISingleFilterOfStringFilter - roles_none: ISingleFilterOfStringFilter - roles_any: Boolean - AND: [UserFilter!] - OR: [UserFilter!] -} - -input AddressFilter { - is_primary: Boolean - is_primary_not: Boolean - street: String - street_contains: String - street_ends_with: String - street_in: [String] - street_not: String - street_not_contains: String - street_not_ends_with: String - street_not_in: [String] - street_not_starts_with: String - street_starts_with: String - AND: [AddressFilter!] - OR: [AddressFilter!] -} - -input ISingleFilterOfStringFilter { - AND: [ISingleFilterOfStringFilter!] - element: String - element_contains: String - element_ends_with: String - element_in: [String] - element_not: String - element_not_contains: String46 - element_not_ends_with: String - element_not_in: [String] - element_not_starts_with: String - element_starts_with: String - OR: [ISingleFilterOfStringFilter!] -} -``` - -### Array Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of array filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.List(x => x.Addresses) - .AllowSome().And() - .AlloAny().And() - .AllowAll().And() - .AllowNone(); - descriptor.List(x => x.Roles) - .AllowSome().And() - .AlloAny().And() - .AllowAll().And() - .AllowNone(); - } -} -``` - -# Naming Conventions - -\_Hot Chocolate already provides two naming schemes for filters. If you would like to define your own naming scheme or extend existing ones have a look at the documentation of TODO:Link-Filtering - -## Snake Case - -**Configuration** -You can configure the Snake Case with the `UseSnakeCase` extension method convention on the `IFilterConventionDescriptor` - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - descriptor.UseSnakeCase() - } -} - -SchemaBuilder.New().AddConvention(); -// -SchemaBuilder.New().AddConvention(new FilterConvention(x => x.UseSnakeCase()) -``` - -```graphql -input FooBarFilter { - AND: [FooBarFilter!] - nested: String - nested_contains: String - nested_ends_with: String - nested_in: [String] - nested_not: String - nested_not_contains: String - nested_not_ends_with: String -**Change the name of an operation** - nested_not_in: [String] - nested_not_starts_with: String - nested_starts_with: String - OR: [FooBarFilter!] -} - -input FooFilter { - AND: [FooFilter!] - bool: Boolean - bool_not: Boolean - comparable: Short - comparableEnumerable_all: ISingleFilterOfInt16Filter - comparableEnumerable_any: Boolean - comparableEnumerable_none: ISingleFilterOfInt16Filter - comparableEnumerable_some: ISingleFilterOfInt16Filter - comparable_gt: Short - comparable_gte: Short - comparable_in: [Short!] - comparable_lt: Short - comparable_lte: Short - comparable_not: Short - comparable_not_gt: Short - comparable_not_gte: Short - comparable_not_in: [Short!] - comparable_not_lt: Short - comparable_not_lte: Short - object: FooBarFilter - OR: [FooFilter!] -} - -input ISingleFilterOfInt16Filter { - AND: [ISingleFilterOfInt16Filter!] - element: Short - element_gt: Short - element_gte: Short - element_in: [Short!] - element_lt: Short - element_lte: Short - element_not: Short - element_not_gt: Short - element_not_gte: Short - element_not_in: [Short!] - element_not_lt: Short - element_not_lte: Short - OR: [ISingleFilterOfInt16Filter!] -} -``` - -## Pascal Case - -**Configuration** -You can configure the Pascal Case with the `UsePascalCase` extension method convention on the `IFilterConventionDescriptor` - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - descriptor.UsePascalCase() - } -} - -SchemaBuilder.New().AddConvention(); -// -SchemaBuilder.New().AddConvention(new FilterConvention(x => x.UsePascalCase()) -``` - -```graphql -input FooBarFilter { - AND: [FooBarFilter!] - Nested: String - Nested_Contains: String - Nested_EndsWith: String - Nested_In: [String] - Nested_Not: String - Nested_Not_Contains: String - Nested_Not_EndsWith: String - Nested_Not_In: [String] - Nested_Not_StartsWith: String - Nested_StartsWith: String - OR: [FooBarFilter!] -} - -input FooFilter { - AND: [FooFilter!] - Bool: Boolean - Bool_Not: Boolean - Comparable: Short - ComparableEnumerable_All: ISingleFilterOfInt16Filter - ComparableEnumerable_Any: Boolean - ComparableEnumerable_None: ISingleFilterOfInt16Filter - ComparableEnumerable_Some: ISingleFilterOfInt16Filter - Comparable_Gt: Short - Comparable_Gte: Short - Comparable_In: [Short!] - Comparable_Lt: Short - Comparable_Lte: Short - Comparable_Not: Short - Comparable_Not_Gt: Short - Comparable_Not_Gte: Short - Comparable_Not_In: [Short!] - Comparable_Not_Lt: Short - Comparable_Not_Lte: Short - Object: FooBarFilter - OR: [FooFilter!] -} - -input ISingleFilterOfInt16Filter { - AND: [ISingleFilterOfInt16Filter!] - Element: Short - Element_Gt: Short - Element_Gte: Short - Element_In: [Short!] - Element_Lt: Short - Element_Lte: Short - Element_Not_Gt: Short - Element_Not: Short - Element_Not_Gte: Short - Element_Not_In: [Short!] - Element_Not_Lt: Short - Element_Not_Lte: Short - OR: [ISingleFilterOfInt16Filter!] -} -``` - -# Customizing Filter - -Hot Chocolate provides different APIs to customize filtering. You can write custom filter input types, customize the inference behavior of .NET Objects, customize the generated expression, or create a custom visitor, and attach your exotic database. - -**As this can be a bit overwhelming the following questionnaire might help:** - -| | | -| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| _You do not want all the generated filters and only allow a specific set of filters in a specific case?_ | Custom FilterInputType | -| _You want to change the name of a field or a whole type?_ | Custom FilterInputType | -| _You want to change the name of the `where` argument?_ | Filter Conventions ArgumentName | -| _You want to configure how_Hot Chocolate_generates the name and the description of filters in globally? e.g. `PascalCaseFilterType`?_ | Filter Conventions | -| _You want to configure what the different types of filters are allowed globally?_ | Filter Conventions | -| _Your database provider does not support certain operations of `IQueryable`_ | Filter Conventions | -| _You want to change the naming of a specific lar filter type? e.g._ `foo_contains` _should be_ `foo_like` | Filter Conventions | -| _You want to customize the expression a filter is generating: e.g._ `_equals` _should not be case sensitive?_ | Expression Visitor  | -| _You want to create your own filter types with custom parameters and custom expressions? e.g. GeoJson?_ | Filter Conventions | -| _You have a database client that does not support `IQueryable` and wants to generate filters for it?_ | Custom Visitor | - -# Custom FilterInputType - -Under the hood, filtering is based on top of normal Hot Chocolate input types. You can easily customize them with a very familiar fluent interface. The filter input types follow the same `descriptor` scheme as you are used to from the normal filter input types. Just extend the base class `FilterInputType` and override the descriptor method. - -```csharp -public class User -{ - public string Name {get; set; } - - public string LastName {get; set; } -} - -public class UserFilterType - : FilterInputType -{ - protected override void Configure( IFilterInputTypeDescriptor descriptor) { - - } -} -``` - -`IFilterInputTypeDescriptor` supports most of the methods of `IInputTypeDescriptor` and adds the configuration interface for the filters. By default, Hot Chocolate generates filters for all properties of the type. -If you do want to specify the filters by yourself you can change this behavior with `BindFields`, `BindFieldsExplicitly` or `BindFieldsImplicitly`. - -```csharp -public class UserFilterType - : FilterInputType -{ - protected override void Configure( IFilterInputTypeDescriptor descriptor) { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name); - } -} -``` - -```graphql -input UserFilter { - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -To add or customize a filter you must use `Filter(x => x.Foo)` for scalars `List(x => x.Bar)` for lists and `Object(x => x.Baz)` for nested objects. -These methods will return fluent interfaces to configure the filter for the selected field. - -A field has different filter operations that you can configure. You will find more about filter types and filter operations here TODO:Link -When fields are bound implicitly, meaning filters are added for all properties, you may want to hide a few fields. You can do this with `Ignore(x => Bar)`. -Operations on fields can again be bound implicitly or explicitly. By default, Hot Chocolate generates operations for all fields of the type. -If you do want to specify the operations by yourself you can change this behavior with `BindFilters`, `BindFiltersExplicitly` or `BindFiltersImplicitly`. - -It is also possible to customize the GraphQL field of the operation further. You can change the name, add a description or directive. - -```csharp -public class UserFilterType - : FilterInputType -{ - protected override void Configure( IFilterInputTypeDescriptor descriptor) { - // descriptor.BindFieldsImplicitly(); <- is already the default - descriptor.Filter(x => x.Name) - .BindFilterExplicitly() - .AllowContains() - .Description("Checks if the provided string is contained in the `Name` of a User") - .And() - .AllowEquals() - .Name("exits_with_name") - .Directive("name"); - descriptor.Ignore(x => x.Bar); - } -} -``` - -```graphql -input UserFilter { - exits_with_name: String @name - """ - Checks if the provided string is contained in the `Name` of a User - """ - name_contains: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -**API Documentation** - -| Method | Description | -| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `csharp±BindFields(BindingBehavior bindingBehavior)` | Defines the filter binding behavior. `Explicitly`or `Implicitly`. Default is `Implicitly` | -| `csharp±BindFieldsExplicitly` | Defines that all filters have to be specified explicitly. This means that only the filters are applied that are added with `Filter(x => x.Foo)` | -| `csharp±BindFieldsImplicitly` | The filter type will add filters for all compatible fields. | -| `csharp±Description(string value)` | Adds explanatory text of the `FilterInputType` that can be accessed via introspection. | -| `csharp±Name(NameString value)` | Defines the _GraphQL_ name of the `FilterInputType`. | -| `csharp±Ignore( Expression> property);` | Ignore the specified property. | -| `csharp±Filter( Expression> property)` | Defines a string filter for the selected property. | -| `csharp±Filter( Expression> property)` | Defines a bool filter for the selected property. | -| `csharp±Filter( Expression> property)` | Defines a comparable filter for the selected property. | -| `csharp±Object( Expression> property)` | Defines a object filter for the selected property. | -| `csharp±List( Expression>> property)` | Defines an array string filter for the selected property. | -| `csharp±List( Expression>> property)` | Defines an array bool filter for the selected property. | -| `csharp±List( Expression>> property)` | Defines an array comparable filter for the selected property. | -| `csharp±Filter( Expression>> property)` | Defines an array object filter for the selected property. | -| `csharp±Directive(TDirective directiveInstance)` | Add directive `directiveInstance` to the type | -| `csharp±Directive(TDirective directiveInstance)` | Add directive of type `TDirective` to the type | -| `csharp±Directive(NameString name, params ArgumentNode[] arguments)` | Add directive of type `TDirective` to the type | - -# Filter Conventions - -The customization of filters with `FilterInputTypes` works if you only want to customize one specific filter type. -If you want to change the behavior of all filter types, you want to create a convention for your filters. The filter convention comes with a fluent interface that is close to a type descriptor. -You can see the convention as a configuration object that holds the state that is used by the type system or the execution engine. - -## Get Started - -To use a filter convention, you can extend `FilterConvention` and override the `Configure` method. Alternatively, you can directly configure the convention over the constructor argument. -You then must register your custom convention on the schema builder with `AddConvention`. - -```csharp -public class CustomConvention - : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) { } -} - -SchemaBuilder.New().AddConvention(); -// -SchemaBuilder.New().AddConvention(new FilterConvention(x => /* Config */)); -``` - -## Convention Descriptor Basics - -In this section, we will take a look at the basic features of the filter convention. -The documentation will reference often to `descriptor`. Imagine this `descriptor` as the parameter of the Configure method of the filter convention in the following context: - -```csharp {5} -public class CustomConvention - : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor - ) { } -} - -SchemaBuilder.New().AddConvention(); -``` - -
- -### Argument Name - -With the convention descriptor, you can easily change the argument name of the `FilterInputType`. - -**Configuration** - -```csharp -descriptor.ArgumentName("example_argument_name"); -``` - -**Result** - -```graphql -type Query { - users(example_argument_name: UserFilter): [User] -} -``` - -### Change Name of Scalar List Type Element - -You can change the name of the element of the list type. - -**Configuration** - -```csharp -descriptor.ElementName("example_element_name"); -``` - -**Result** - -```graphql -input ISingleFilterOfInt16Filter { - AND: [ISingleFilterOfInt16Filter!] - example_element_name: Short - example_element_name_gt: Short - example_element_name_gte: Short - example_element_name_in: [Short!] - example_element_name_lt: Short - example_element_name_lte: Short - example_element_name_not: Short - example_element_name_not_gt: Short - example_element_name_not_gte: Short - example_element_name_not_in: [Short!] - example_element_name_not_lt: Short - example_element_name_not_lte: Short - OR: [ISingleFilterOfInt16Filter!] -} -``` - -### Configure Filter Type Name Globally - -You can change the way Hot Chocolate names the types by supplying a delegate. - -This delegate must be of the following type: - -```csharp -public delegate NameString GetFilterTypeName( - IDescriptorContext context, - Type entityType); -``` - -**Configuration** - -```csharp -descriptor.TypeName((context,types) => - context.Naming.GetTypeName(entityType, TypeKind.Object) + "Custom"); -``` - -**Result** - -```graphql -type Query { - users(where: UserCustom): [User] -} -``` - -### Configure Filter Description Globally - -To change the way filter types are named, you have to exchange the factory. - -You have to provide a delegate of the following type: - -```csharp -public delegate string GetFilterTypeDescription( - IDescriptorContext context, - Type entityType); -``` - -**Configuration** - -```csharp -descriptor.TypeName((context,types) => - context.Naming.GetTypeDescription(entityType, TypeKind.Object); + "Custom"); -``` - -**Result** - -```graphql -""" -Custom -""" -input UserFilter { - AND: [UserFilter!] - isOnline: Boolean - isOnline_not: Boolean - OR: [UserFilter!] -} -``` - -### Reset Configuration - -Hot Chocolate shippes with well-defined defaults. To start from scratch, you need to call `Reset()`first. - -**Configuration** - -```csharp -descriptor.Reset(); -``` - -**Result** - -> **⚠ Note:** You will need to add a complete configuration, otherwise the filter will not work as desired! - -## Describe with convention - -With the filter convention descriptor, you have full control over what filters are inferred, their names, operations, and a lot more. -The convention provides a familiar interface to the type configuration. We recommended to first take a look at `Filter & Operations` to understand the concept of filters. This will help you understand how the filter configuration works. - -Filtering has two core components at its heart. First, you have the inference of filters based on .NET types. The second part is an interceptor that translates the filters to the desired output and applies it to the resolver pipeline. These two parts can (and have to) be configured completely independently. With this separation, it is possible to easily extend the behavior. The descriptor is designed to be extendable by extension methods. - -**It's fluent** - -Filter conventions are a completely fluent experience. You can write a whole configuration as a chain of method calls. -This provides a very clean interface, but can, on the other hand, get messy quickly. We recommend using indentation to keep the configuration comprehensible. -You can drill up with `And()`. - -```csharp - descriptor.Operation(FilterOperationKind.Equals).Description("has to be equal"); - descriptor.Operation(FilterOperationKind.NotEquals).Description("has not to be equal"); - descriptor.Type(FilterKind.Comparable).Operation(FilterOperationKind.NotEquals).Description("has to be comparable and not equal") - - - descriptor - .Operation(FilterOperationKind.Equals) - .Description("has to be equal") - .And() - .Operation(FilterOperationKind.NotEquals) - .Description("has not to be equal") - .And() - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.NotEquals) - .Description("has to be comparable and not equal") -``` - -### Configuration of the type system - -In this section, we will focus on the generation of the schema. If you are interested in changing how filters translate to the database, you have to look here TODO:Link - -#### Configure Filter Operations - -There are two ways to configure Operations. - -You can configure a default configuration that applies to all operations of this kind. In this case the configuration for `FilterOperationKind.Equals` would be applied to all `FilterKind` that specify this operation. - -```csharp - descriptor.Operation(FilterOperationKind.Equals) -``` - -If you want to configure a more specific Operation e.g. `FilterOperationKind.Equal` of kind `FilterKind.String`, you can override the default behavior. - -```csharp - descriptor.Type(FilterKind.String).Operation(FilterOperationKind.Equals) -``` - -The operation descriptor allows you to configure the name, the description or even ignore an operation completely - -In this example, we will look at the following input type: - -```graphql -input UserFilter { - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - loggingCount_not: Int - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Change the name of an operation - -To change the name of an operation you need to specify a delegate of the following type: - -```csharp -public delegate NameString CreateFieldName( - FilterFieldDefintion definition, - FilterOperationKind kind); -``` - -**Configuration** - -```csharp {1, 6} - // (A) - // specifies that all not equals operations should be extended with _nada - descriptor - .Operation(FilterOperationKind.NotEquals) - .Name((def, kind) => def.Name + "_nada" ); - // (B) - // specifies that the not equals operations should be extended with _niente. - // this overrides (A) - descriptor - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.NotEquals) - .Name((def, kind) => def.Name + "_niente" ) -``` - -**result** - -```graphql {8,18} -input UserFilter { - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - loggingCount_niente: Int <-- (B) - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_nada: String <-- (A) - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Change the description of an operation - -In the same way, you can configure names you can also configure the description of operations. -You can either set the description for all operations of this kind or only for a specific one in combination with a filter kind. - -**Configuration** - -```csharp - descriptor - .Operation(FilterOperationKind.Equals) - .Description("has to be equal") - .And() - .Operation(FilterOperationKind.NotEquals) - .Description("has not to be equal") - .And() - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.NotEquals) - .Description("has to be comparable and not equal") -``` - -**result** - -```graphql {2-4,11-14, 20-22,27-29} -input UserFilter { - """ - has to be equal - """ - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - """ - has to be comparable and not equal - """ - loggingCount_not: Int - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - """ - has to be equal - """ - name: String - name_contains: String - name_ends_with: String - name_in: [String] - """ - has not to be equal - """ - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Hide Operations - -Hot Chocolate comes pre-configured with a set of operations. If you like to hide operations globally, you can use `Ignore` for it. -If your database provider does not support certain `IQueryable` methods you can just ignore the operation. Ignored operations do not generate filter input types. - -There are multiple ways to ignore an operation: - -**Configuration** - -```csharp - descriptor - .Ignore(FilterOperationKind.Equals) - .Operation(FilterOperationKind.NotEquals) - .Ignore() - .And() - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.GreaterThanOrEqual) - .Ignore(); -``` - -**result** - -```graphql {2,4, 8,14,18} -input UserFilter { - ↵ - loggingCount_gt: Int - ↵ - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - ↵ - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - ↵ - name_contains: String - name_ends_with: String - name_in: [String] - ↵ - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Configure Implicit Filter - -The default binding behavior of Hot Chocolate is implicit. Filter types are no exception. -This first may seem like magic, but unfortunately, there is none. It is just code. With `AddImplicitFilter` you can add this pinch of magic to your extension too. -Hot Chocolate creates the filters as it builds the input type. The type iterates over a list of factories sequentially and tries to create a definition for each property. The first factory that can handle the property wins and creates a definition for the filter. - -To configure you have to use the following delegate: - -```csharp - public delegate bool TryCreateImplicitFilter( - IDescriptorContext context, - Type type, - PropertyInfo property, - IFilterConvention filterConventions, - [NotNullWhen(true)] out FilterFieldDefintion? definition); -``` - -| parameter | type | description | -| ------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------- | -| _context_ | `IDescriptorContext` | The context of the type descriptor | -| _type_ | `Type` | The type of the property. `Nullable` is already unwrapped (typeof(T)) | -| _property_ | `PropertyInfo` | The property | -| _filterConventions_ | `IFilterConvention` | The instance of the `IFilterContention`. | -| _definition_ | `out FilterFieldDefintion?` | The generated definition for the property. Return null if the current factory cannot handle the property. | - -If you just want to build your extension for implicit bindings, you can just out a custom `FilterFieldDefinition`. - -It makes sense to encapsulate that logic in a FilterFieldDescriptor though. You can reuse this descriptor also for the fluent configuration interface. - -**Example** - -```csharp -private static bool TryCreateStringFilter( - IDescriptorContext context, - Type type, - PropertyInfo property, - IFilterConvention filterConventions, - [NotNullWhen(true)] out FilterFieldDefintion? definition) -{ - if (type == typeof(string)) - { - var field = new StringFilterFieldDescriptor(context, property, filterConventions); - definition = field.CreateDefinition(); - return true; - } - - definition = null; - return false; -} -``` - -##### Creating a fluent filter extension - -Hot Chocolate provides fluent interfaces for all its APIs. If you want to create an extension that integrates seamlessly with Hot Chocolate it makes sense to also provide fluent interfaces. It makes sense to briefly understand how `Type -> Descriptor -> Definition` work. You can read more about it here //TODO LINK - -Here a quick introduction: - -_Type_ - -A type is a description of a GraphQL Type System Object. Hot Chocolate builds types during schema creation. Types specify how a GraphQL Type looks like. It holds, for example, the definition, fields, interfaces, and all life cycle methods. Type do only exist on startup; they do not exist on runtime. - -_Type Definition_ - -Each type has a definition that describes the type. It holds, for example, the name, description, the CLR type and the field definitions. The field definitions describe the fields that are on the type. - -_Type Descriptor_ - -A type descriptor is a fluent interface to describe the type over the definition. The type descriptor does not have access to the type itself. It operates solely on the definition. - -In the case of filtering, this works nearly the same. The `FilterInputType` is just an extension of the `InputObjectType`. It also has the same _Definition_. The `FilterInputType` stores `FilterOperationField` on this definition. These are extensions of the normal `InputField`'s and extend it by a `FilterOperationKind`. - -With a normal `InputTypeDescriptor` you declare a field by selecting a member. The filter descriptor works a little differently. You declare the `FilterKind` of a member by selecting it and then you declare the operations on this filter. These operations are the input field configuration. - -```csharp -InputTypeDescriptor inputDesc; -inputDesc.Field(x => x.Name) - .Description("This is the name") - - -FilterInputTypeDescriptor inputDesc; -inputDesc.Filter(x => x.Name).AllowEqual().Description("This is the name") -``` - -We have a few case studies that will show you how you can change the inference: - -1. String "\_like" shows an example of how you can easily add a "\_like" operation to the string filter -2. DateTime "from", "to" -3. NetTopologySuite - -> The configuration you see in this case study only shows how you add an operation to an already-existing filter. After this, the job is only half way done. To create a working filter, you must also change the expression visitor. Check the documentation for //TODO: ExpressionVisitor - -##### Case Study: String "\_like" - -**Situation** -The customer has requested a full-text search of the description field of a product. The product owner has promised the feature to the customer two sprints ago and it has still not been shipped. The UX guru of your company has, slightly under pressure, worked out a solution, and together with the frontend team they have already build a prototype. In the heat of the moment, they did not read the user story correctly and, unfortunately, realized last minute that the current filtering API does not fit their needs. The customer does also has to be able to create complex search queries. `This%Test` should match `This is a Test`. As you come back from lunch a hysterical product owner explains the situation to you. To you, it is immediately clear that this can be easily done by using the SQL `like` operator. - -In your codebase you use the `UseFiltering` middleware extensively. In some cases, you also have customized filter types. To cover all possible cases you need - -1. Implicit Binding: `[UseFiltering]` should automagically create the "\_like" filter for every string filter -2. Explicity Binding: `desc.Filter(x => x.Description).AllowLike())` -3. Expression Visitor: You want to directly filter on the database. You use EF Core. - -**Implicit Binding** -With the conventions, it is easy to add operations on already existing filters. We will first look into the configuration for filter inference and in a second step into the code first extension. - -You just need to navigate to the filter you like to modify. `descriptor.Type(FilterKind.String)`. Just add the operation you need with `.Operation(FilterOperationKind.Like)`. The next step is to add factories for the name and the description. - -Altogether this looks like this: - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - descriptor - .Type(FilterKind.String) - .Operation(FilterOperationKind.GreaterThanOrEqual) - .Name((def, kind) => def.Name + "_like" ); - .Description("Full text search. Use % as a placeholder for any symbol"); - } -} -``` - -**Explicit Binding** -By extending the filter descriptor of the string filter you can add a fluent extension that seamlessly integrated with the Hot Chocolate API. - -//TODO: currently there `StringFilterOperationDescriptor` requires `StringFilterFieldDescriptor` instead of `StringFilterFieldDescriptor` and there is no way to `Allow` -//TODO: TYPO ! FilterFieldDefintion -//TODO: Move RewriteType to convention . -//TODO: Move up CreateFieldName - -```csharp -public static class StringLikeFilterExtension -{ - public static IStringFilterOperationDescriptor AllowLike( - IStringFilterFieldDescriptor descriptor) - { - return descriptor.Allow( - FilterOperationKind.ArrayAll, - (ctx, definition) => - { - var operation = new FilterOperation( - typeof(string), FilterOperationKind.ArrayAll, definition.Property); - - return StringFilterOperationDescriptor.New( - ctx, - descriptor, - ctx.GetFilterConvention().CreateFieldName(FilterOperationKind.ArrayAll), - ctx.GetFilterConvention().RewriteType(FilterOperationKind.ArrayAll), - operation); - } - ) - } -} -``` - ---- - -##### Case Study: DateTime "from", "to" - -**Situation** - -1. Implicit Binding: `[UseFiltering]` should automagically create `DateTimeFilter` and the corresponding "\_from" and "\_to". -2. Explicity Binding: `desc.Filter(x => x.OrderedAt).AllowFrom().AllowTo())` -3. Expression Visitor: You want to directly filter on the database. You use EF Core. - -**Configuration** - -It is slightly more complex to create a custom filter than just modifying existing operations. There are a few different parts that must come together to make this work. Implicit and Explicit Bindings are coming together in this example. - -Let's start with the configuration of the convention. By splitting the configuration up into a set of extension methods that can be applied to the convention, it is possible to easily replace sub-components of the extension. e.g. some users might want to use an expression visitor, some others might want to use MognoDB Native. - -- `UseDateTimeFilter` adds support for date-time filters and registers the expression visitor for it. Abstraction for `UseDateTimeFilterImplicitly().UseDateTimeExpression()` - -- `UseDateTimeFilterImplicitly` only registers the configuration of the schema building part of the extension - -- `UseDateTimeExpression` only registers the expression visitor configuration. - -With this separation, a user that prefers to use a custom visitor, can just register the types and skip the expression visitor configuration - -TODO: UseExpressionVisitor should return expression visitor if it already exists -TODO: Reference Definition from Filter Operation instead of property. This way we could reduce complexity further and improve extensibility - -```csharp -public static class DateTimeFilterConventionExtensions -{ - public static IFilterConventionDescriptor UseDateTimeFilter( - this IFilterConventionDescriptor descriptor) => - descriptor.UseDateTimeFilterImplicitly() - .UseDateTimeFilterExpression(); - - public static IFilterConventionDescriptor UseDateTimeFilterImplicitly( - this IFilterConventionDescriptor descriptor) => - descriptor.AddImplicitFilter(TryCreateDateTimeFilter) - .Type(FilterKind.DateTime) - .Operation(FilterOperationKind.GreaterThanOrEquals) - .Name((def, _) => def.Name + "_from") - .Description("") - .And() - .Operation(FilterOperationKind.LowerThanOrEquals) - .Name((def, _) => def.Name + "_to") - .Description("") - .And() - .And(); - - public static IFilterConventionDescriptor UseDateTimeFilterExpression( - this IFilterConventionDescriptor descriptor) => - descriptor.UseExpressionVisitor() - .Kind(FilterKind.DateTime) - .Operation(FilterOperationKind.LowerThanOrEquals) - .Handler(ComparableOperationHandlers.LowerThanOrEquals).And() - .Operation(FilterOperationKind.GreaterThanOrEquals) - .Handler(ComparableOperationHandlers.GreaterThanOrEquals).And() - .And() - .And(); -} -``` - -**Create Date Time Filter Implicitly** - -`DateTime` is a new filter. Hot Chocolate is only aware of its existence because of the delegate passed to `AddImplicitFilter` - -```csharp -private static bool TryCreateDateTimeFilter( - IDescriptorContext context, - Type type, - PropertyInfo property, - IFilterConvention filterConventions, - [NotNullWhen(true)] out FilterFieldDefintion? definition) -{ - if (type == typeof(DateTime)) - { - var field = new DateTimeFilterFieldDescriptor( - context, property, filterConventions); - definition = field.CreateDefinition(); - return true; - } - - definition = null; - return false; -} -``` - -TODO: make filters name based -**Filter Field** - -A filter field is a collection of operations. It holds the configuration of the different operations like _“from”_ and _“to”_. In classic Hot Chocolate fashion there is a descriptor that describes these collections. Hot Chocolate provides the base class `FilterFieldDescriptorBase` you can use as an extension point. There is quite a lot of boilerplate code you need to write. e.g. it makes sense to define an interface for the descriptor. -You find an example here: //TODO LINK - -For the explicit binding, we need to override `CreateOperationDefinition`. In case the filter is bound implicitly, this method is invoked for each operation. -TODO: I think there is an issue with AllowNotEndsWith. - -```csharp -// We override this method for implicity binding -protected override FilterOperationDefintion CreateOperationDefinition( - FilterOperationKind operationKind) => - CreateOperation(operationKind).CreateDefinition(); -``` - -For the implicit binding, we only need to add the methods `AllowFrom` and `AllowTo`. - -```csharp -// The following to methods are for adding the filters explicitly -public IDateTimeFilterOperationDescriptor AllowFrom() => - GetOrCreateOperation(FilterOperationKind.GreaterThanOrEqual); - -public IDateTimeFilterOperationDescriptor AllowTo() => - GetOrCreateOperation(FilterOperationKind.LowerThanOrEqual); - -// This is just a little helper that reduces code duplication -private DateTimeFilterOperationDescriptor GetOrCreateOperation( - FilterOperationKind operationKind) => - Filters.GetOrAddOperation(operationKind, - () => CreateOperation(operationKind)); -``` - -All the methods described above call `CreateOperation`. This method creates the operation descriptor. The `FitlerOperation` that is created here, will also be available for the expression visitor. - -```csharp -// This helper method creates the operation. -private DateTimeFilterOperationDescriptor CreateOperation( - FilterOperationKind operationKind) - { - // This operation is also available in execution. - var operation = new FilterOperation( - typeof(DateTime), - Definition.Kind, - operationKind, - Definition.Property); - - return DateTimeOffsetFilterOperationDescriptor.New( - Context, - this, - CreateFieldName(operationKind), - RewriteType(operationKind), - operation, - FilterConvention); - } -``` - -**Filter Operation** - -In this example; there are two filter operations _"form"_ and _"to"_. The configuration with a descriptor combines explicit and implicit binding. As a base class, you can use `FilterOperationDescriptorBase`. -Here is the interface that is used in this example: - -```csharp -public interface IDateTimeFilterOperationDescriptor - : IDescriptor - , IFluent - { - /// Define filter operations for another field. - IDateTimeFilterFieldDescriptor And(); - - /// Specify the name of the filter operation. - IDateTimeFilterOperationDescriptor Name(NameString value); - - /// Specify the description of the filter operation. - IDateTimeFilterOperationDescriptor Description(string value); - - /// Annotate the operation filter field with a directive. - IDateTimeFilterOperationDescriptor Directive(T directiveInstance) - where T : class; - IDateTimeFilterOperationDescriptor Directive() - where T : class, new(); - IDateTimeFilterOperationDescriptor Directive( - NameString name, - params ArgumentNode[] arguments); - } -``` - -You can find the implementation of this interface here: //TODO link - -**Filter Type Extension** -The last missing piece to complete the integration into Hot Chocolate is an extension of `FilterInputType`. This can again be done as a extension method. - -```csharp -public IStringFilterFieldDescriptor Filter( - Expression> property) -{ - if (property.ExtractMember() is PropertyInfo p) - { - return Fields.GetOrAddDescriptor(p, - () => new StringFilterFieldDescriptor(Context, p)); - } - - throw new ArgumentException( - FilterResources.FilterInputTypeDescriptor_OnlyProperties, - nameof(property)); -} -``` - -//TODO Open this api - ---- - -##### Case Study: Filters for NetTopologySuite - -**Situation** - -> **Note:** If you are searching for `NetTopologySuite`, they are already implemented. Have a look at//TODO LINK - -1. Implicit Binding: `[UseFiltering]` should automagically create `Point` and the corresponding "\_distance" -2. Explicity Binding: `desc.Filter(x => x.Location).AllowDistance()` -3. Expression Visitor: You want to directly filter on the database. You use EF Core. - -Things are different in this case, as there is no longer a 1:1 mapping of input type to method or property. Imagine you want to fetch all bakeries that are near you. In C# you would write something like `dbContext.Bakeries.Where(x => x.Location.Distance(me.Location) < 5)`. This cannot be translated to a _GraphQL_ input type directly. - -A _GraphQL_ query might look like this. - -```graphql -{ - bakeries( - where: { location: { distance: { from: { x: 32, y: 15 }, is_lt: 5 } } } - ) { - name - } -} -``` - -_GraphQL_ input fields cannot have arguments. To work around this issue a data structure is needed that combines the filter payload and the operation. The input type for this example has the following structure. - -```csharp -public class FilterDistance -{ - - public FilterDistance( - FilterPointData from) - { - From = from; - } - /// contains the x and y coordinates. - public FilterPointData From { get; } - - public double Is { get; set; } -} -``` - -```graphql -input FilterDistanceInput { - from: FilterPointDataInput! - is: Float - is_gt: Float - is_gte: Float - is_lt: Float - is_lte: Float - is_in: Float - is_not: Float - is_not_gt: Float - is_not_gte: Float - is_not_lt: Float - is_not_lte: Float - is_not_in: Float -} -``` - -//TODO: Add skip / inopfield! - -Hot Chocolate would generate nested filters for the payload property "From" by default. This can be avoided by declaring the field as input payload. - -```csharp -public class DistanceFilterType - : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.Input(x => x.From); - descriptor.Filter(x => x.Is); - } -} -``` - -**Convention & Implicit Factory & Type Descriptor** - -The configuration of the convention, the implicit type factory and the descriptors are very similar to the the two examples before. To not bloat the documentation with duplication we just refer to these two examples and to the reference implementation here //TODO LINK - ---- - -## Translating Filters - -Hot Chocolate can translate incoming filters requests directly onto collections or even on to the database. In the default implementation, the output of this translation is a Linq expression that can be applied to `IQueryable` and `IEnumerable`. You can choose to change the expression that is generated or can even create custom output. Hot Chocolate is using visitors to translate input objects. - -[Learn more about visitors here](/docs/hotchocolate/v11/api-reference/visitors). - -### Expression Filters - -Filter conventions make it easier to change how an expression should be generated. There are three different extension points you can use to change the behavior of the expression visitor. You do not have to worry about the visiting of the input object itself. - -#### Describe the Expression Visitor - -The expression visitor descriptor is accessible through the filter convention. By calling `UseExpressionVisitor` on the convention descriptor you gain access. The expression visitor has the default set of expressions pre-configured. - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor) - { - descriptor.UseExpressionVisitor() - } -} -``` - -The descriptor provides a fluent interface that is very similar to the one of the convention descriptor itself. You have to specify what _operation_ on which _filter kind_ you want to configure. You can drill with `Kind` and `Operation` and go back up by calling `And()`: - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor) - { - descriptor - .UseExpressionVisitor() - .Kind(FilterKind.String) - .Operation(FilterKind.Equals) - .And() - .And() - .Kind(FilterKind.Comparable) - .Operation(FilterKind.In) - } -} -``` - -**Visitation Flow** - -The expression visitor starts as any other visitor at the node you pass in. Usually, this is the node object value node of the filter argument. It then starts the visitation. Every time the visitor _enters_ or _leaves_ an object field, it looks for a matching configuration. If there is no special _enter_ behavior of a field, the visitor generates the expression for the combination of _kind_ and _operation_. - -The next two paragraphs show how the algorithm works in detail. - -_Enter_ - -On _entering_ a field, the visitor tries to get a `FilterFieldEnter` delegate for the `FilterKind` of the current field. If a delegate was found, executed, and the execution return true, the `Enter` method returns the _action_ specified by the delegate. In all other cases, the visitor tries to execute an `OperationHandler` for the combination `FilterKind` and `OperationKind`. If the handler returns true, the expression returned by the handler is added to the context. - -1. Let _field_ be the field that is visited -1. Let _kind_ be the `FilterKind` of _field_ -1. Let _operation_ be the `FilterOperationKind` of _field_ -1. Let _convention_ be the `FilterConvention` used by this visitor -1. Let _enterField_ be the `FilterFieldEnter` delegate for _kind_ on _convention_ -1. If _enterField_ is not null: - 1. Let _action_ be the visitor action of _enterField_ - 1. If _enterField_ returns true: - 1. **return** _action_ -1. Let _operationHander_ be the `FilterOperationHandler` delegate for (_kind_, _operation_) on _convention_ -1. If _operationHandler_ is not null: - 1. Let _expression_ be the expression generated by _operationHandler_ - 1. If _enterField_ returns true: - 1. enqueue _expression_ -1. **return** `SkipAndLeave` - -_Leave_ - -On _entering_ a field, the visitor tries to get and execute a `FilterFieldLeave` delegate for the `FilterKind` of the current field. - -1. Let _field_ be the field that is visited -1. Let _kind_ be the `FilterKind` of _field_ -1. Let _operation_ be the `FilterOperationKind` of _field_ -1. Let _convention_ be the `FilterConvention` used by this visitor -1. Let _leaveField_ be the `FilterFieldLeave` delegate for _kind_ on _convention_ -1. If _leaveField_ is not null: - 1. Execute _leaveField_ - -**Operations** - -The operation descriptor provides you with the method `Handler`. With this method, you can configure, how the expression for the _operation_ of this filter _kind_ is generated. You have to pass a delegate of the following type: - -```csharp -public delegate bool FilterOperationHandler( - FilterOperation operation, - IInputType type, - IValueNode value, - IQueryableFilterVisitorContext context, - [NotNullWhen(true)]out Expression? result); -``` - -This delegate might seem intimidating first, but it is not bad as it looks. If this delegate `true` the `out Expression?` is enqueued on the filters. This means that the visitor will pick it up as it composes the filters. - -| Parameter | Description | -| ---------------------------------------- | --------------------------------------- | -| `FilterOperation operation` | The operation of the current field | -| `IInputType type` | The input type of the current field | -| `IValueNode value` | The AST value node of the current field | -| `IQueryableFilterVisitorContext context` | The context that builds up the state | -| `out Expression? result` | The generated expression | - -Operations handlers can be configured like the following: - -```csharp {10,13} -public class CustomConvention : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor) - { - descriptor - .UseExpressionVisitor() - .Kind(FilterKind.String) - .Operation(FilterKind.Equals) - .Handler(YourVeryOwnHandler.HandleEquals) - .And() - .Operation(FilterKind.NotEquals) - .Handler(YourVeryOwnHandler.HandleNotEquals) - } -} -``` - -TODO: add example - -**Kind** - -There are two extension points on each _filter kind_. You can alter the _entering_ of a filter and the _leaving_. - -**Enter** -You can configure the entering with the following delegate: - -```csharp -public delegate bool FilterFieldEnter( - FilterOperationField field, - ObjectFieldNode node, - IQueryableFilterVisitorContext context, - [NotNullWhen(true)]out ISyntaxVisitorAction? action); -``` - -If this field returns _true_ the filter visitor will continue visitation with the specified _action_ in the out parameter `action`. [Check out the visitor documentation for all possible actions](http://addlinkshere). -If the field does not return true and a visitor action, the visitor will continue and search for a _operation handler_. After this, the visitor will continue with `SkipAndLeave`. - -| Parameter | Description | -| ---------------------------------------- | ------------------------------------ | -| `FilterOperationField field` | The current field | -| `ObjectFieldNode node` | The object node of the current field | -| `IQueryableFilterVisitorContext context` | The context that builds up the state | -| `out ISyntaxVisitorAction? action` | The visitor action | - -**Leave** -You can configure the entering with the following delegate: - -```csharp -public delegate void FilterFieldLeave( - FilterOperationField field, - ObjectFieldNode node, - IQueryableFilterVisitorContext context); -``` - -| Parameter | Description | -| ---------------------------------------- | ------------------------------------ | -| `FilterOperationField field` | The current field | -| `ObjectFieldNode node` | The object node of the current field | -| `IQueryableFilterVisitorContext context` | The context that builds up the state | diff --git a/website/src/docs/hotchocolate/v11/api-reference/language.md b/website/src/docs/hotchocolate/v11/api-reference/language.md index c10b44dcf0a..42359107510 100644 --- a/website/src/docs/hotchocolate/v11/api-reference/language.md +++ b/website/src/docs/hotchocolate/v11/api-reference/language.md @@ -53,7 +53,7 @@ This interface defines the `NodeKind` of the node. | Variable | [A variable](https://spec.graphql.org/June2018/#sec-Language.Variables) | Query (out) | \$foo | | SelectionSet | [specifies a selection of _Field_, _FragmentSpread_ or _InlineFragment_](https://spec.graphql.org/June2018/#sec-Selection-Sets) | Query (out) | {foo bar} | | Field | [Describes a field as a part of a selection set](https://spec.graphql.org/June2018/#sec-Language.Fields) | Query (out) | foo | -| FragmentSpread | [Denotes a spread of a `FragemntDefinition`](https://spec.graphql.org/June2018/#FragmentSpread) | Query (out) | ...f1 | +| FragmentSpread | [Denotes a spread of a `FragmentDefinition`](https://spec.graphql.org/June2018/#FragmentSpread) | Query (out) | ...f1 | | InlineFragment | [Denotes an inline fragment](https://spec.graphql.org/June2018/#sec-Inline-Fragments) | Query (out) | ... on Foo { bar} | | FragmentDefinition | [Defines the definition of a fragment](https://spec.graphql.org/June2018/#FragmentDefinition) | Query (out) | fragment f1 on Foo {} | | IntValue | [Denotes a `int` value](https://spec.graphql.org/June2018/#sec-Int-Value) | Query (in) | 1 | diff --git a/website/src/docs/hotchocolate/v11/api-reference/visitors.md b/website/src/docs/hotchocolate/v11/api-reference/visitors.md index 53a66773f5d..a7e4b81b95e 100644 --- a/website/src/docs/hotchocolate/v11/api-reference/visitors.md +++ b/website/src/docs/hotchocolate/v11/api-reference/visitors.md @@ -147,3 +147,5 @@ query { } } ``` + + diff --git a/website/src/docs/hotchocolate/v11/defining-a-schema/directives.md b/website/src/docs/hotchocolate/v11/defining-a-schema/directives.md index 602ddfdffc9..9fb91da2a2f 100644 --- a/website/src/docs/hotchocolate/v11/defining-a-schema/directives.md +++ b/website/src/docs/hotchocolate/v11/defining-a-schema/directives.md @@ -304,3 +304,5 @@ If there were more directives in the query, they would be appended to the direct So, now the order would be like the following: `a, b, c, d, e, f`. Every middleware can execute the original resolver function by calling `ResolveAsync()` on the `IDirectiveContext`. + + diff --git a/website/src/docs/hotchocolate/v11/defining-a-schema/scalars.md b/website/src/docs/hotchocolate/v11/defining-a-schema/scalars.md index 88f1e32f62d..3b10bfa8923 100644 --- a/website/src/docs/hotchocolate/v11/defining-a-schema/scalars.md +++ b/website/src/docs/hotchocolate/v11/defining-a-schema/scalars.md @@ -2,7 +2,7 @@ title: "Scalars" --- -Scalar types are the primitives of our schema and can hold a specific type of data. They are leaf types, meaning we cannot use e.g. `{ fieldname }` to further drill down into the type. The main purpose of a scalar is to define how a value is serialized and deserialized. +Scalar types are the primitives of our schema and can hold a specific type of data. They are leaf types, meaning we cannot use e.g. `{ fieldName }` to further drill down into the type. The main purpose of a scalar is to define how a value is serialized and deserialized. Besides basic scalars like `String` and `Int`, we can also create custom scalars like `CreditCardNumber` or `SocialSecurityNumber`. These custom scalars can greatly enhance the expressiveness of our schema and help new developers to get a grasp of our API. diff --git a/website/src/docs/hotchocolate/v11/distributed-schema/index.md b/website/src/docs/hotchocolate/v11/distributed-schema/index.md index 000768bf9e4..cfaec163f9d 100644 --- a/website/src/docs/hotchocolate/v11/distributed-schema/index.md +++ b/website/src/docs/hotchocolate/v11/distributed-schema/index.md @@ -32,7 +32,7 @@ _Schema of the Address Service_ ```sdl type Query { - addressOfPerson(persondId: ID!): Address + addressOfPerson(personId: ID!): Address } type Address { diff --git a/website/src/docs/hotchocolate/v11/distributed-schema/schema-configuration.md b/website/src/docs/hotchocolate/v11/distributed-schema/schema-configuration.md index 42cad53df2b..6c80e7cf28a 100644 --- a/website/src/docs/hotchocolate/v11/distributed-schema/schema-configuration.md +++ b/website/src/docs/hotchocolate/v11/distributed-schema/schema-configuration.md @@ -67,7 +67,7 @@ In schema stitching type renames can be defined on the gateway: services .AddGraphQLServer() .AddRemoteSchema(Products) - .AddRemoteSchema(Inventiory) + .AddRemoteSchema(Inventory) .RenameType("Category","ProductCategory", Products); ``` @@ -136,7 +136,7 @@ In schema stitching field renames can be defined on the gateway: services .AddGraphQLServer() .AddRemoteSchema(Products) - .AddRemoteSchema(Inventiory) + .AddRemoteSchema(Inventory) .RenameField("Query", "categories", "productCategories", schemaName: Products) ``` @@ -182,7 +182,7 @@ If you want to remove a specific type from the schema you can also use `IgnoreTy services .AddGraphQLServer() .AddRemoteSchema(Products) - .AddRemoteSchema(Inventiory) + .AddRemoteSchema(Inventory) .IgnoreType("Category", schemaName: Products); ``` @@ -215,7 +215,7 @@ This can be useful when you want to merge root fields of domain services, but ig services .AddGraphQLServer() .AddRemoteSchema(Products) - .AddRemoteSchema(Inventiory) + .AddRemoteSchema(Inventory) .IgnoreField("Query", "categories", Products) .IgnoreField("Query", "categories", Inventory); ``` @@ -252,7 +252,7 @@ type InventoryInfo { type Query { inventoryInfo(upc: Int!): InventoryInfo! - shippingEsitmate(price: Int!, weight: Int!): InventoryInfo! + shippingEstimate(price: Int!, weight: Int!): InventoryInfo! } ``` @@ -453,7 +453,7 @@ It would be better if the middleware is only applied to the field that needs it. You can use a schema interceptor to apply the middleware to the fields that use it. ```csharp -public class MessageMiddlwareInterceptor : TypeInterceptor +public class MessageMiddlewareInterceptor : TypeInterceptor { public override bool CanHandle(ITypeSystemObjectContext context) { @@ -524,7 +524,7 @@ services .PublishSchemaDefinition( c => c .SetName("inventory") - // Ignores the root types. This removes `inStock` and `shippingEsitmate` + // Ignores the root types. This removes `inStock` and `shippingEstimate` // from the `Query` type of the Gateway .IgnoreRootTypes() // Adds a type extension. diff --git a/website/src/docs/hotchocolate/v11/distributed-schema/schema-federations.md b/website/src/docs/hotchocolate/v11/distributed-schema/schema-federations.md index d790ee3ffef..2e3cb379f35 100644 --- a/website/src/docs/hotchocolate/v11/distributed-schema/schema-federations.md +++ b/website/src/docs/hotchocolate/v11/distributed-schema/schema-federations.md @@ -21,7 +21,7 @@ You will need to add a package reference to `HotChocolate.Stitching.Redis` to al A domain service has to _publish the schema definition_. The schema is published on the initialization of the schema. By default, a schema is lazy and only initialized when the first request is sent. -You can also initialize the schema on startup with `IntitializeOnStartup`. +You can also initialize the schema on startup with `InitializeOnStartup`. Every schema requires a unique name. This name is used in several places to reference the schema. By calling `PublishSchemaDefinition` you can configure how the schema should be published. diff --git a/website/src/docs/hotchocolate/v11/fetching-data/fetching-from-rest.md b/website/src/docs/hotchocolate/v11/fetching-data/fetching-from-rest.md index ec18fb7f276..690eb3236a1 100644 --- a/website/src/docs/hotchocolate/v11/fetching-data/fetching-from-rest.md +++ b/website/src/docs/hotchocolate/v11/fetching-data/fetching-from-rest.md @@ -247,3 +247,5 @@ You can now head over to your Banana Cake Pop on your GraphQL Server (/graphql) } } ``` + + diff --git a/website/src/docs/hotchocolate/v11/fetching-data/filtering.md b/website/src/docs/hotchocolate/v11/fetching-data/filtering.md index 0e02c386b32..a731f2664b0 100644 --- a/website/src/docs/hotchocolate/v11/fetching-data/filtering.md +++ b/website/src/docs/hotchocolate/v11/fetching-data/filtering.md @@ -297,7 +297,7 @@ input UserFilterInput { ## Comparable Filter -Defines filters for `IComparables` like: `bool`, `byte`, `shot`, `int`, `long`, `float`, `double` `decimal`, `Guid`, `DateTime`, `DateTimeOffset` and `TimeSpan` +Defines filters for `IComparable`s like: `bool`, `byte`, `shot`, `int`, `long`, `float`, `double` `decimal`, `Guid`, `DateTime`, `DateTimeOffset` and `TimeSpan` ```csharp public class User diff --git a/website/src/docs/hotchocolate/v11/fetching-data/pagination.md b/website/src/docs/hotchocolate/v11/fetching-data/pagination.md index 435dd07ea59..cf0e21ad56a 100644 --- a/website/src/docs/hotchocolate/v11/fetching-data/pagination.md +++ b/website/src/docs/hotchocolate/v11/fetching-data/pagination.md @@ -51,7 +51,7 @@ Adding pagination capabilities to our fields is a breeze. All we have to do is a public class Query { [UsePaging] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) => repository.GetUsers(); } ``` @@ -77,7 +77,7 @@ public class QueryType : ObjectType .UsePaging() .Resolve(context => { - var repository = context.Service(); + var repository = context.Service(); return repository.GetUsers(); }); @@ -264,7 +264,7 @@ To add _offset-based_ pagination capabilities to our fields we have to add the ` public class Query { [UseOffsetPaging] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) => repository.GetUsers(); } ``` @@ -290,7 +290,7 @@ public class QueryType : ObjectType .UseOffsetPaging() .Resolve(context => { - var repository = context.Service(); + var repository = context.Service(); return repository.GetUsers(); }); diff --git a/website/src/docs/hotchocolate/v11/fetching-data/resolvers.md b/website/src/docs/hotchocolate/v11/fetching-data/resolvers.md index 8579288bc7a..a8df318b338 100644 --- a/website/src/docs/hotchocolate/v11/fetching-data/resolvers.md +++ b/website/src/docs/hotchocolate/v11/fetching-data/resolvers.md @@ -249,7 +249,7 @@ There are also specific arguments that will be automatically populated by Hot Ch # Injecting Services -Resolvers integrate nicely with `Microsoft.Extensions.DependecyInjection`. +Resolvers integrate nicely with `Microsoft.Extensions.DependencyInjection`. We can access all registered services in our resolvers. Let's assume we have created a `UserService` and registered it as a service. diff --git a/website/src/docs/hotchocolate/v11/fetching-data/sorting.md b/website/src/docs/hotchocolate/v11/fetching-data/sorting.md index cdbf5ee3452..a0ac08fa0a0 100644 --- a/website/src/docs/hotchocolate/v11/fetching-data/sorting.md +++ b/website/src/docs/hotchocolate/v11/fetching-data/sorting.md @@ -376,7 +376,7 @@ When you build extensions for sorting, you may want to modify or extend the `Def ```csharp descriptor.ConfigureEnum( - x => x.Operaion(CustomOperations.NULL_FIRST).Name("NULL_FIRST)); + x => x.Operation(CustomOperations.NULL_FIRST).Name("NULL_FIRST)); ``` ```sdl @@ -394,11 +394,11 @@ You can use `Configure()` to alter the configuration of a type. ```csharp descriptor.Configure( - x => x.Description("This is my custome description")); + x => x.Description("This is my custom description")); ``` ```sdl -"This is my customer description" +"This is my custom description" input CustomSortInputType { name: SortEnumType } diff --git a/website/src/docs/hotchocolate/v11/integrations/entity-framework.md b/website/src/docs/hotchocolate/v11/integrations/entity-framework.md index b7ecfd87872..996300c3c7b 100644 --- a/website/src/docs/hotchocolate/v11/integrations/entity-framework.md +++ b/website/src/docs/hotchocolate/v11/integrations/entity-framework.md @@ -5,7 +5,7 @@ title: Entity Framework The execution engine of Hot Chocolate executes resolvers in parallel. This can lead to exceptions because the database context of Entity Framework cannot handle more than one request in parallel. So if you are seeing exceptions like `A second operation started on this context before a previous operation completed.` -or `Cannot access a disposed object...` the `HotChocolate.Data.EnityFramework` package has you back. +or `Cannot access a disposed object...` the `HotChocolate.Data.EntityFramework` package has you back. It provides helpers that make EF integration with Hot Chocolate a breeze. The package was build on the foundation of EntityFramework Core v5.0.0. diff --git a/website/src/docs/hotchocolate/v11/integrations/index.md b/website/src/docs/hotchocolate/v11/integrations/index.md index 0a9323fb8f0..3124f171b27 100644 --- a/website/src/docs/hotchocolate/v11/integrations/index.md +++ b/website/src/docs/hotchocolate/v11/integrations/index.md @@ -12,7 +12,7 @@ The package `HotChocolate.Data.EntityFramework` helps you to manage the `DBConte # MongoDB -With the package `HotChocoalte.Data.MongoDb` you can integrate your MongoDB with ease. +With the package `HotChocolate.Data.MongoDb` you can integrate your MongoDB with ease. This package adds support for filtering, sorting, projection and pagination with native MongoDB queries. [Learn more about our MongoDB integration](/docs/hotchocolate/v11/integrations/mongodb) diff --git a/website/src/docs/hotchocolate/v11/integrations/mongodb.md b/website/src/docs/hotchocolate/v11/integrations/mongodb.md index adc9339044d..c5eda2adabb 100644 --- a/website/src/docs/hotchocolate/v11/integrations/mongodb.md +++ b/website/src/docs/hotchocolate/v11/integrations/mongodb.md @@ -253,3 +253,5 @@ public IExecutable GetPersonById( return collection.Find(x => x.Id == id).AsExecutable(); } ``` + + diff --git a/website/src/docs/hotchocolate/v11/integrations/spatial-data.md b/website/src/docs/hotchocolate/v11/integrations/spatial-data.md index 8c613218742..582eaf80403 100644 --- a/website/src/docs/hotchocolate/v11/integrations/spatial-data.md +++ b/website/src/docs/hotchocolate/v11/integrations/spatial-data.md @@ -147,7 +147,7 @@ interface GeoJSONInterface { } ``` -A `NetTopologySuite.Gemeotry` is mapped to this interface by default. +A `NetTopologySuite.Geometry` is mapped to this interface by default. ## Input Types @@ -588,3 +588,5 @@ Additionally we want to provide a way for users, to specify in what CRS they wan Currently we only support filtering for spatial data. We also want to provide a way for users to sort results. This can e.g. be used to find the nearest result for a given point. + + diff --git a/website/src/docs/hotchocolate/v11/performance/automatic-persisted-queries.md b/website/src/docs/hotchocolate/v11/performance/automatic-persisted-queries.md index 5854b54faa9..90e8b5eb1af 100644 --- a/website/src/docs/hotchocolate/v11/performance/automatic-persisted-queries.md +++ b/website/src/docs/hotchocolate/v11/performance/automatic-persisted-queries.md @@ -293,3 +293,5 @@ curl -g 'http://localhost:5000/graphql/?extensions={"persistedQuery":{"version": ```json { "data": { "__typename": "Query" } } ``` + + diff --git a/website/src/docs/hotchocolate/v12/api-reference/apollo-federation.md b/website/src/docs/hotchocolate/v12/api-reference/apollo-federation.md index ae68dea6feb..b8410942eb2 100644 --- a/website/src/docs/hotchocolate/v12/api-reference/apollo-federation.md +++ b/website/src/docs/hotchocolate/v12/api-reference/apollo-federation.md @@ -325,7 +325,7 @@ services.AddGraphQLServer() ## Testing and executing your reference resolvers -After creating an entity, you'll likely wonder "how do I invoke and test this reference resolver?" Entities that define a reference resolver can be queried through the [auto-generated `_entites` query](https://www.apollographql.com/docs/federation/subgraph-spec#understanding-query_entities) at the subgraph level. +After creating an entity, you'll likely wonder "how do I invoke and test this reference resolver?" Entities that define a reference resolver can be queried through the [auto-generated `_entities` query](https://www.apollographql.com/docs/federation/subgraph-spec#understanding-query_entities) at the subgraph level. You'll invoke the query by providing an array of representations using a combination of a `__typename` and key field values to invoke the appropriate resolver. An example query for our `Product` would look something like the following. @@ -435,7 +435,7 @@ services.AddGraphQLServer() -Next, we'll create our `Review` type that has a reference to the `Product` entity. Similar to our first class, we'll need to denote the type's key(s) and the corresponding entity reference resovler(s). +Next, we'll create our `Review` type that has a reference to the `Product` entity. Similar to our first class, we'll need to denote the type's key(s) and the corresponding entity reference resolver(s). diff --git a/website/src/docs/hotchocolate/v12/api-reference/aspnetcore.md b/website/src/docs/hotchocolate/v12/api-reference/aspnetcore.md index cea326b6376..2476f34d89e 100644 --- a/website/src/docs/hotchocolate/v12/api-reference/aspnetcore.md +++ b/website/src/docs/hotchocolate/v12/api-reference/aspnetcore.md @@ -392,3 +392,5 @@ public class MyCustomHttpResultSerializer : DefaultHttpResultSerializer } } ``` + + diff --git a/website/src/docs/hotchocolate/v12/api-reference/executable.md b/website/src/docs/hotchocolate/v12/api-reference/executable.md index d366ed0fb8e..4a378d98e62 100644 --- a/website/src/docs/hotchocolate/v12/api-reference/executable.md +++ b/website/src/docs/hotchocolate/v12/api-reference/executable.md @@ -13,14 +13,14 @@ public class User public string Name { get; } } -public interface IUserRepostiory +public interface IUserRepository { public IExecutable FindAll(); } public class Query { - public IExecutable GetUsers([Service] IUserRepostiory repo) => + public IExecutable GetUsers([Service] IUserRepository repo) => repo.FindAll(); } ``` diff --git a/website/src/docs/hotchocolate/v12/api-reference/extending-filtering.md b/website/src/docs/hotchocolate/v12/api-reference/extending-filtering.md index 681c415b79b..2ab4cad9596 100644 --- a/website/src/docs/hotchocolate/v12/api-reference/extending-filtering.md +++ b/website/src/docs/hotchocolate/v12/api-reference/extending-filtering.md @@ -158,7 +158,7 @@ public class CustomConvention : FilterConvention { protected override void Configure(IFilterConventionDescriptor descriptor) { - desciptor.AddDefaults(); + descriptor.AddDefaults(); } public override NameString GetTypeName(Type runtimeType) => @@ -257,13 +257,13 @@ You can override `TryHandleOperation` to handle operations. ## The Context -As the visitor and the field handlers are singletons, a context object is passed along with the traversion of input objects. +As the visitor and the field handlers are singletons, a context object is passed along with the traversal of input objects. Field handlers can push data on this context, to make it available for other handlers further down in the tree. The context contains `Types`, `Operations`, `Errors` and `Scopes`. It is very provider-specific what data you need to store in the context. In the case of the `IQueryable` provider, it also contains `RuntimeTypes` and knows if the source is `InMemory` or a database call. -With `Scopes` it is possible to add multiple logical layers to a context. In the case of `IQuerable` this is needed, whenever a new closure starts: +With `Scopes` it is possible to add multiple logical layers to a context. In the case of `IQueryable` this is needed, whenever a new closure starts: ```csharp // /------------------------ SCOPE 1 -----------------------------\ @@ -306,7 +306,7 @@ To make it simpler, this is roughly what occurs during the visitation: # instance[0] = y # level[0] = [] any: { - # Push poperty Address.Street onto the scope + # Push property Address.Street onto the scope # instance[1] = y.Street # level[1] = [] street: { @@ -315,7 +315,7 @@ To make it simpler, this is roughly what occurs during the visitation: # level[2] = [y.Street == "221B Baker Street"] eq: "221B Baker Street" } - # Combine everything of the current level and pop the porperty street from the instance + # Combine everything of the current level and pop the property street from the instance # instance[1] = y.Street # level[1] = [y.Street == "221B Baker Street"] } @@ -323,11 +323,11 @@ To make it simpler, this is roughly what occurs during the visitation: # instance[2] = x.Company.Addresses # level[2] = [x.Company.Addresses.Any(y => y.Street == "221B Baker Street")] } - # Combine everything of the current level and pop the porperty street from the instance + # Combine everything of the current level and pop the property street from the instance # instance[1] = x.Company # level[1] = [x.Company.Addresses.Any(y => y.Street == "221B Baker Street")] } - # Combine everything of the current level and pop the porperty street from the instance + # Combine everything of the current level and pop the property street from the instance # instance[0] = x # level[0] = [x.Company.Addresses.Any(y => y.Street == "221B Baker Street")] } @@ -366,7 +366,7 @@ public class QueryableStringInvariantEqualsHandler : QueryableStringOperationHan IValueNode value, object parsedValue) { - // We get the instance of the context. This is the expression path to the propert + // We get the instance of the context. This is the expression path to the property // e.g. ~> y.Street Expression property = context.GetInstance(); @@ -374,7 +374,7 @@ public class QueryableStringInvariantEqualsHandler : QueryableStringOperationHan // e.g. ~> eq: "221B Baker Street" if (parsedValue is string str) { - // Creates and returnes the operation + // Creates and returns the operation // e.g. ~> y.Street.ToLower() == "221b baker street" return Expression.Equal( Expression.Call(property, _toLower), diff --git a/website/src/docs/hotchocolate/v12/api-reference/filtering.md b/website/src/docs/hotchocolate/v12/api-reference/filtering.md deleted file mode 100644 index 3cf4554b1c4..00000000000 --- a/website/src/docs/hotchocolate/v12/api-reference/filtering.md +++ /dev/null @@ -1,2031 +0,0 @@ ---- -title: Filtering ---- - -**What are filters?** - -With Hot Chocolate filters, you can expose complex filter objects through your GraphQL API that translates to native database queries. - -The default filter implementation translates filters to expression trees and applies these on `IQueryable`. - -# Overview - -Filters by default work on `IQueryable` but you can also easily customize them to use other interfaces. - -Hot Chocolate by default will inspect your .NET model and infer the possible filter operations from it. - -The following type would yield the following filter operations: - -```csharp -public class Foo -{ - public string Bar { get; set; } -} -``` - -```graphql -input FooFilter { - bar: String - bar_contains: String - bar_ends_with: String - bar_in: [String] - bar_not: String - bar_not_contains: String - bar_not_ends_with: String - bar_not_in: [String] - bar_not_starts_with: String - bar_starts_with: String - AND: [FooFilter!] - OR: [FooFilter!] -} -``` - -**So how can we get started with filters?** - -Getting started with filters is very easy, especially if you do not want to explicitly define filters or customize anything. - -Hot Chocolate will infer the filters directly from your .Net Model and then use a Middleware to apply filters to `IQueryable` or `IEnumerable` on execution. - -> ⚠️ **Note:** If you use more than middleware, keep in mind that **ORDER MATTERS**. - -> ⚠️ **Note:** Be sure to install the `HotChocolate.Types.Filters` NuGet package. - -In the following example, the person resolver returns the `IQueryable` representing the data source. The `IQueryable` represents a not executed database query on which Hot Chocolate can apply filters. - -**Code First** - -The next thing to note is the `UseFiltering` extension method which adds the filter argument to the field and a middleware that can apply those filters to the `IQueryable`. The execution engine will, in the end, execute the `IQueryable` and fetch the data. - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPersons(default)) - .Type>>() - .UseFiltering(); - } -} - -public class Query -{ - public IQueryable GetPersons([Service]IPersonRepository repository) - { - repository.GetPersons(); - } -} -``` - -**Pure Code First** - -The field descriptor attribute `[UseFiltering]` does apply the extension method `UseFiltering()` on the field descriptor. - -```csharp -public class Query -{ - [UseFiltering] - public IQueryable GetPersons([Service]IPersonRepository repository) - { - repository.GetPersons(); - } -} -``` - -**Schema First** - -> ⚠️ **Note:** Schema first does currently not support filtering! - -# Customizing Filters - -A `FilterInputType` defines a GraphQL input type, that Hot Chocolate uses for filtering. You can customize these similar to a normal input type. You can change the name of the type; add, remove, or change operations or directive; and configure the binding behavior. To define and customize a filter we must inherit from `FilterInputType` and configure it like any other type by overriding the `Configure` method. - -```csharp -public class PersonFilterType - : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor - .BindFieldsExplicitly() - .Filter(t => t.Name) - .BindOperationsExplicitly() - .AllowEquals().Name("equals").And() - .AllowContains().Name("contains").And() - .AllowIn().Name("in"); - } -} -``` - -The above filter type defines explicitly which fields allow filtering and what operations these filters allow. Additionally, the filter type changes the name of the equals operation of the filter of the field `Name` to `equals`. - -To make use of the configuration in this filter type, you can provide it to the `UseFiltering` extension method as the generic type argument. - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPerson(default)) - .Type>>(); - .UseFiltering() - } -} -``` - -# Sorting - -Like with filter support you can add sorting support to your database queries. - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPerson(default)) - .Type>>(); - .UseSorting() - } -} -``` - -> Warning: Be sure to install the `HotChocolate.Types.Sorting` NuGet package. - -If you want to combine for instance paging, filtering, and sorting make sure that the order is like follows: - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPerson(default)) - .UsePaging() - .UseFiltering() - .UseSorting(); - } -} -``` - -**Why is order important?** - -Paging, filtering, and sorting are modular middlewares that form the field resolver pipeline. - -The above example forms the following pipeline: - -`Paging -> Filtering -> Sorting -> Field Resolver` - -The paging middleware will first delegate to the next middleware, which is filtering. - -The filtering middleware will also first delegate to the next middleware, which is sorting. - -The sorting middleware will again first delegate to the next middleware, which is the actual field resolver. - -The field resolver will call `GetPerson` which returns in this example an `IQueryable`. The queryable represents a not yet executed database query. - -After the resolver has been executed and puts its result onto the middleware context the sorting middleware will apply for the sort order on the query. - -After the sorting middleware has been executed and updated the result on the middleware context the filtering middleware will apply its filters on the queryable and updates the result on the middleware context. - -After the paging middleware has been executed and updated the result on the middleware context the paging middleware will slice the data and execute the queryable which will then actually pull in data from the data source. - -So, if we, for instance, applied paging as our last middleware the data set would have been sliced first and then filtered which in most cases is not what we actually want. - -# Filter & Operations Kinds - -You can break down filtering into different kinds of filters that then have different operations. -The filter kind is bound to the type. A string is fundamentally something different than an array or an object. -Each filter kind has different operations that you can apply to it. Some operations are unique to a filter and some operations are shared across multiple filter -e.g. A string filter has string specific operations like `Contains` or `EndsWith` but still shares the operations `Equals` and `NotEquals` with the boolean filter. - -## Filter Kinds - -Hot Chocolate knows following filter kinds - -| Kind | Operations | -| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| String | Equals, In, EndsWith, StartsWith, Contains, NotEquals, NotIn, NotEndsWith, NotStartsWith, NotContains | -| Bool | Equals, NotEquals | -| Object | Equals | -| Array | Some, Any, All, None | -| Comparable | Equals, In, GreaterThan, GreaterThanOrEqual, LowerThan, LowerThanOrEqual, NotEquals, NotIn, NotGreaterThan, NotGreaterThanOrEqual, NotLowerThan, NotLowerThanOrEqual | - -## Operations Kinds - -Hot Chocolate knows following operation kinds - -| Kind | Operations | -| ---------------------- | ----------------------------------------------------------------------------------------------------- | -| Equals | Compares the equality of input value and property value | -| NotEquals | negation of Equals | -| In | Checks if the property value is contained in a given list of input values | -| NotIn | negation of In | -| GreaterThan | checks if the input value is greater than the property value | -| NotGreaterThan | negation of GreaterThan | -| GreaterThanOrEquals | checks if the input value is greater than or equal to the property value | -| NotGreaterThanOrEquals | negation of GreaterThanOrEquals | -| LowerThan | checks if the input value is lower than the property value | -| NotLowerThan | negation of LowerThan | -| LowerThanOrEquals | checks if the input value is lower than or equal to the property value | -| NotLowerThanOrEquals | negation of LowerThanOrEquals | -| EndsWith | checks if the property value ends with the input value | -| NotEndsWith | negation of EndsWith | -| StartsWith | checks if the property value starts with the input value | -| NotStartsWith | negation of StartsWith | -| Contains | checks if the input value is contained in the property value | -| NotContains | negation of Contains | -| Some | checks if at least one element in the collection exists | -| Some | checks if at least one element of the property value meets the condition provided by the input value | -| None | checks if no element of the property value meets the condition provided by the input value | -| All | checks if all least one element of the property value meets the condition provided by the input value | - -## Boolean Filter - -In this example, we look at the filter configuration of a Boolean filter. -As an example, we will use the following model: - -```csharp -public class User -{ - public bool IsOnline {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - isOnline: Boolean -} - -input UserFilter { - isOnline: Boolean - isOnline_not: Boolean - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -### Boolean Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of Boolean filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name) - .AllowEquals().And() - .AllowNotEquals(); - } -} -``` - -## Comparable Filter - -In this example, we look at the filter configuration of a comparable filter. - -A comparable filter is generated for all values that implement IComparable except string and boolean. -e.g. `csharp±enum`, `csharp±int`, `csharp±DateTime`... - -As an example, we will use the following model: - -```csharp -public class User -{ - public int LoggingCount {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - loggingCount: Int -} - -input UserFilter { - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - loggingCount_not: Int - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -### Comparable Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of comparable filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name) - .AllowEquals().And() - .AllowNotEquals().And() - .AllowGreaterThan().And() - .AllowNotGreaterThan().And() - .AllowGreaterThanOrEqals().And() - .AllowNotGreaterThanOrEqals().And() - .AllowLowerThan().And() - .AllowNotLowerThan().And() - .AllowLowerThanOrEqals().And() - .AllowNotLowerThanOrEqals().And() - .AllowIn().And() - .AllowNotIn(); - } -} -``` - -## String Filter - -In this example, we look at the filter configuration of a String filter. -As an example, we will use the following model: - -```csharp -public class User -{ - public string Name {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - name: String -} - -input UserFilter { - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -### String Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of string filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name) - .AllowEquals().And() - .AllowNotEquals().And() - .AllowContains().And() - .AllowNotContains().And() - .AllowStartsWith().And() - .AllowNotStartsWith().And() - .AllowEndsWith().And() - .AllowNotEndsWith().And() - .AllowIn().And() - .AllowNotIn(); - } -} -``` - -## Object Filter - -In this example, we look at the filter configuration of an object filter. - -Hot Chocolate generated object filters for all objects. Since Version 11, Hot Chocolate also generates filter types for nested objects. You can also use object filters to filter over database relations. - -As an example, we will use the following model: - -```csharp -public class User -{ - public Address Address {get;set;} -} - -public class Address -{ - public string Street {get;set;} - - public bool IsPrimary {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - address: Address -} - -type Address { - isPrimary: Boolean - street: String -} - -input UserFilter { - address: AddressFilter - AND: [UserFilter!] - OR: [UserFilter!] -} - -input AddressFilter { - is_primary: Boolean - is_primary_not: Boolean - street: String - street_contains: String - street_ends_with: String - street_in: [String] - street_not: String - street_not_contains: String - street_not_ends_with: String - street_not_in: [String] - street_not_starts_with: String - street_starts_with: String - AND: [AddressFilter!] - OR: [AddressFilter!] -} -``` - -### Object Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of comparable filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Object(x => x.Address); - } -} -``` - -**Configuring a custom nested filter type:** - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Object(x => x.Address).AllowObject(); - } -} - -public class AddressFilterType : FilterInputType
-{ - protected override void Configure( - IFilterInputTypeDescriptor
descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.IsPrimary); - } -} - -// or inline - -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Object(x => x.Address) - .AllowObject( - y => y.BindFieldsExplicitly().Filter(z => z.IsPrimary)); - } -} - - -``` - -## List Filter - -In this example, we look at the filter configuration of a list filter. - -Hot Chocolate can also generate filters for `IEnumerable`s. Like object filter, Hot Chocolate generates filters for the whole object tree. List filter addresses scalars and object values differently. -In the case the field is a scalar value, Hot Chocolate creates and object type to address the different operations of this scalar. e.g. If you specify filters for a list of strings, Hot Chocolate creates an object type that contains all operations of the string filter. -In case the list holds a complex object, it generates an object filter for this object instead. - -Hot Chocolate implicitly generates filters for all properties that implement `IEnumerable`. -e.g. `csharp±string[]`, `csharp±List`, `csharp±IEnumerable`... - -As an example, we will use the following model: - -```csharp -public class User -{ - public string[] Roles {get;set;} - - public IEnumerable
Addresses {get;set;} -} - -public class Address -{ - public string Street {get;set;} - - public bool IsPrimary {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - addresses: [Address] - roles: [String] -} - -type Address { - isPrimary: Boolean - street: String -} - -input UserFilter { - addresses_some: AddressFilter - addresses_all: AddressFilter - addresses_none: AddressFilter - addresses_any: Boolean - roles_some: ISingleFilterOfStringFilter - roles_all: ISingleFilterOfStringFilter - roles_none: ISingleFilterOfStringFilter - roles_any: Boolean - AND: [UserFilter!] - OR: [UserFilter!] -} - -input AddressFilter { - is_primary: Boolean - is_primary_not: Boolean - street: String - street_contains: String - street_ends_with: String - street_in: [String] - street_not: String - street_not_contains: String - street_not_ends_with: String - street_not_in: [String] - street_not_starts_with: String - street_starts_with: String - AND: [AddressFilter!] - OR: [AddressFilter!] -} - -input ISingleFilterOfStringFilter { - AND: [ISingleFilterOfStringFilter!] - element: String - element_contains: String - element_ends_with: String - element_in: [String] - element_not: String - element_not_contains: String46 - element_not_ends_with: String - element_not_in: [String] - element_not_starts_with: String - element_starts_with: String - OR: [ISingleFilterOfStringFilter!] -} -``` - -### Array Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of array filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.List(x => x.Addresses) - .AllowSome().And() - .AlloAny().And() - .AllowAll().And() - .AllowNone(); - descriptor.List(x => x.Roles) - .AllowSome().And() - .AlloAny().And() - .AllowAll().And() - .AllowNone(); - } -} -``` - -# Naming Conventions - -\_Hot Chocolate already provides two naming schemes for filters. If you would like to define your own naming scheme or extend existing ones have a look at the documentation of TODO:Link-Filtering - -## Snake Case - -**Configuration** -You can configure the Snake Case with the `UseSnakeCase` extension method convention on the `IFilterConventionDescriptor` - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - descriptor.UseSnakeCase() - } -} - -SchemaBuilder.New().AddConvention(); -// -SchemaBuilder.New().AddConvention(new FilterConvention(x => x.UseSnakeCase()) -``` - -```graphql -input FooBarFilter { - AND: [FooBarFilter!] - nested: String - nested_contains: String - nested_ends_with: String - nested_in: [String] - nested_not: String - nested_not_contains: String - nested_not_ends_with: String -**Change the name of an operation** - nested_not_in: [String] - nested_not_starts_with: String - nested_starts_with: String - OR: [FooBarFilter!] -} - -input FooFilter { - AND: [FooFilter!] - bool: Boolean - bool_not: Boolean - comparable: Short - comparableEnumerable_all: ISingleFilterOfInt16Filter - comparableEnumerable_any: Boolean - comparableEnumerable_none: ISingleFilterOfInt16Filter - comparableEnumerable_some: ISingleFilterOfInt16Filter - comparable_gt: Short - comparable_gte: Short - comparable_in: [Short!] - comparable_lt: Short - comparable_lte: Short - comparable_not: Short - comparable_not_gt: Short - comparable_not_gte: Short - comparable_not_in: [Short!] - comparable_not_lt: Short - comparable_not_lte: Short - object: FooBarFilter - OR: [FooFilter!] -} - -input ISingleFilterOfInt16Filter { - AND: [ISingleFilterOfInt16Filter!] - element: Short - element_gt: Short - element_gte: Short - element_in: [Short!] - element_lt: Short - element_lte: Short - element_not: Short - element_not_gt: Short - element_not_gte: Short - element_not_in: [Short!] - element_not_lt: Short - element_not_lte: Short - OR: [ISingleFilterOfInt16Filter!] -} -``` - -## Pascal Case - -**Configuration** -You can configure the Pascal Case with the `UsePascalCase` extension method convention on the `IFilterConventionDescriptor` - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - descriptor.UsePascalCase() - } -} - -SchemaBuilder.New().AddConvention(); -// -SchemaBuilder.New().AddConvention(new FilterConvention(x => x.UsePascalCase()) -``` - -```graphql -input FooBarFilter { - AND: [FooBarFilter!] - Nested: String - Nested_Contains: String - Nested_EndsWith: String - Nested_In: [String] - Nested_Not: String - Nested_Not_Contains: String - Nested_Not_EndsWith: String - Nested_Not_In: [String] - Nested_Not_StartsWith: String - Nested_StartsWith: String - OR: [FooBarFilter!] -} - -input FooFilter { - AND: [FooFilter!] - Bool: Boolean - Bool_Not: Boolean - Comparable: Short - ComparableEnumerable_All: ISingleFilterOfInt16Filter - ComparableEnumerable_Any: Boolean - ComparableEnumerable_None: ISingleFilterOfInt16Filter - ComparableEnumerable_Some: ISingleFilterOfInt16Filter - Comparable_Gt: Short - Comparable_Gte: Short - Comparable_In: [Short!] - Comparable_Lt: Short - Comparable_Lte: Short - Comparable_Not: Short - Comparable_Not_Gt: Short - Comparable_Not_Gte: Short - Comparable_Not_In: [Short!] - Comparable_Not_Lt: Short - Comparable_Not_Lte: Short - Object: FooBarFilter - OR: [FooFilter!] -} - -input ISingleFilterOfInt16Filter { - AND: [ISingleFilterOfInt16Filter!] - Element: Short - Element_Gt: Short - Element_Gte: Short - Element_In: [Short!] - Element_Lt: Short - Element_Lte: Short - Element_Not_Gt: Short - Element_Not: Short - Element_Not_Gte: Short - Element_Not_In: [Short!] - Element_Not_Lt: Short - Element_Not_Lte: Short - OR: [ISingleFilterOfInt16Filter!] -} -``` - -# Customizing Filter - -Hot Chocolate provides different APIs to customize filtering. You can write custom filter input types, customize the inference behavior of .NET Objects, customize the generated expression, or create a custom visitor, and attach your exotic database. - -**As this can be a bit overwhelming the following questionnaire might help:** - -| | | -| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| _You do not want all the generated filters and only allow a specific set of filters in a specific case?_ | Custom FilterInputType | -| _You want to change the name of a field or a whole type?_ | Custom FilterInputType | -| _You want to change the name of the `where` argument?_ | Filter Conventions ArgumentName | -| _You want to configure how_Hot Chocolate_generates the name and the description of filters in globally? e.g. `PascalCaseFilterType`?_ | Filter Conventions | -| _You want to configure what the different types of filters are allowed globally?_ | Filter Conventions | -| _Your database provider does not support certain operations of `IQueryable`_ | Filter Conventions | -| _You want to change the naming of a specific lar filter type? e.g._ `foo_contains` _should be_ `foo_like` | Filter Conventions | -| _You want to customize the expression a filter is generating: e.g._ `_equals` _should not be case sensitive?_ | Expression Visitor  | -| _You want to create your own filter types with custom parameters and custom expressions? e.g. GeoJson?_ | Filter Conventions | -| _You have a database client that does not support `IQueryable` and wants to generate filters for it?_ | Custom Visitor | - -# Custom FilterInputType - -Under the hood, filtering is based on top of normal Hot Chocolate input types. You can easily customize them with a very familiar fluent interface. The filter input types follow the same `descriptor` scheme as you are used to from the normal filter input types. Just extend the base class `FilterInputType` and override the descriptor method. - -```csharp -public class User -{ - public string Name {get; set; } - - public string LastName {get; set; } -} - -public class UserFilterType - : FilterInputType -{ - protected override void Configure( IFilterInputTypeDescriptor descriptor) { - - } -} -``` - -`IFilterInputTypeDescriptor` supports most of the methods of `IInputTypeDescriptor` and adds the configuration interface for the filters. By default, Hot Chocolate generates filters for all properties of the type. -If you do want to specify the filters by yourself you can change this behavior with `BindFields`, `BindFieldsExplicitly` or `BindFieldsImplicitly`. - -```csharp -public class UserFilterType - : FilterInputType -{ - protected override void Configure( IFilterInputTypeDescriptor descriptor) { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name); - } -} -``` - -```graphql -input UserFilter { - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -To add or customize a filter you must use `Filter(x => x.Foo)` for scalars `List(x => x.Bar)` for lists and `Object(x => x.Baz)` for nested objects. -These methods will return fluent interfaces to configure the filter for the selected field. - -A field has different filter operations that you can configure. You will find more about filter types and filter operations here TODO:Link -When fields are bound implicitly, meaning filters are added for all properties, you may want to hide a few fields. You can do this with `Ignore(x => Bar)`. -Operations on fields can again be bound implicitly or explicitly. By default, Hot Chocolate generates operations for all fields of the type. -If you do want to specify the operations by yourself you can change this behavior with `BindFilters`, `BindFiltersExplicitly` or `BindFiltersImplicitly`. - -It is also possible to customize the GraphQL field of the operation further. You can change the name, add a description or directive. - -```csharp -public class UserFilterType - : FilterInputType -{ - protected override void Configure( IFilterInputTypeDescriptor descriptor) { - // descriptor.BindFieldsImplicitly(); <- is already the default - descriptor.Filter(x => x.Name) - .BindFilterExplicitly() - .AllowContains() - .Description("Checks if the provided string is contained in the `Name` of a User") - .And() - .AllowEquals() - .Name("exits_with_name") - .Directive("name"); - descriptor.Ignore(x => x.Bar); - } -} -``` - -```graphql -input UserFilter { - exits_with_name: String @name - """ - Checks if the provided string is contained in the `Name` of a User - """ - name_contains: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -**API Documentation** - -| Method | Description | -| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `csharp±BindFields(BindingBehavior bindingBehavior)` | Defines the filter binding behavior. `Explicitly`or `Implicitly`. Default is `Implicitly` | -| `csharp±BindFieldsExplicitly` | Defines that all filters have to be specified explicitly. This means that only the filters are applied that are added with `Filter(x => x.Foo)` | -| `csharp±BindFieldsImplicitly` | The filter type will add filters for all compatible fields. | -| `csharp±Description(string value)` | Adds explanatory text of the `FilterInputType` that can be accessed via introspection. | -| `csharp±Name(NameString value)` | Defines the _GraphQL_ name of the `FilterInputType`. | -| `csharp±Ignore( Expression> property);` | Ignore the specified property. | -| `csharp±Filter( Expression> property)` | Defines a string filter for the selected property. | -| `csharp±Filter( Expression> property)` | Defines a bool filter for the selected property. | -| `csharp±Filter( Expression> property)` | Defines a comparable filter for the selected property. | -| `csharp±Object( Expression> property)` | Defines a object filter for the selected property. | -| `csharp±List( Expression>> property)` | Defines an array string filter for the selected property. | -| `csharp±List( Expression>> property)` | Defines an array bool filter for the selected property. | -| `csharp±List( Expression>> property)` | Defines an array comparable filter for the selected property. | -| `csharp±Filter( Expression>> property)` | Defines an array object filter for the selected property. | -| `csharp±Directive(TDirective directiveInstance)` | Add directive `directiveInstance` to the type | -| `csharp±Directive(TDirective directiveInstance)` | Add directive of type `TDirective` to the type | -| `csharp±Directive(NameString name, params ArgumentNode[] arguments)` | Add directive of type `TDirective` to the type | - -# Filter Conventions - -The customization of filters with `FilterInputTypes` works if you only want to customize one specific filter type. -If you want to change the behavior of all filter types, you want to create a convention for your filters. The filter convention comes with a fluent interface that is close to a type descriptor. -You can see the convention as a configuration object that holds the state that is used by the type system or the execution engine. - -## Get Started - -To use a filter convention, you can extend `FilterConvention` and override the `Configure` method. Alternatively, you can directly configure the convention over the constructor argument. -You then must register your custom convention on the schema builder with `AddConvention`. - -```csharp -public class CustomConvention - : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) { } -} - -SchemaBuilder.New().AddConvention(); -// -SchemaBuilder.New().AddConvention(new FilterConvention(x => /* Config */)); -``` - -## Convention Descriptor Basics - -In this section, we will take a look at the basic features of the filter convention. -The documentation will reference often to `descriptor`. Imagine this `descriptor` as the parameter of the Configure method of the filter convention in the following context: - -```csharp {5} -public class CustomConvention - : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor - ) { } -} - -SchemaBuilder.New().AddConvention(); -``` - -
- -### Argument Name - -With the convention descriptor, you can easily change the argument name of the `FilterInputType`. - -**Configuration** - -```csharp -descriptor.ArgumentName("example_argument_name"); -``` - -**Result** - -```graphql -type Query { - users(example_argument_name: UserFilter): [User] -} -``` - -### Change Name of Scalar List Type Element - -You can change the name of the element of the list type. - -**Configuration** - -```csharp -descriptor.ElementName("example_element_name"); -``` - -**Result** - -```graphql -input ISingleFilterOfInt16Filter { - AND: [ISingleFilterOfInt16Filter!] - example_element_name: Short - example_element_name_gt: Short - example_element_name_gte: Short - example_element_name_in: [Short!] - example_element_name_lt: Short - example_element_name_lte: Short - example_element_name_not: Short - example_element_name_not_gt: Short - example_element_name_not_gte: Short - example_element_name_not_in: [Short!] - example_element_name_not_lt: Short - example_element_name_not_lte: Short - OR: [ISingleFilterOfInt16Filter!] -} -``` - -### Configure Filter Type Name Globally - -You can change the way Hot Chocolate names the types by supplying a delegate. - -This delegate must be of the following type: - -```csharp -public delegate NameString GetFilterTypeName( - IDescriptorContext context, - Type entityType); -``` - -**Configuration** - -```csharp -descriptor.TypeName((context,types) => - context.Naming.GetTypeName(entityType, TypeKind.Object) + "Custom"); -``` - -**Result** - -```graphql -type Query { - users(where: UserCustom): [User] -} -``` - -### Configure Filter Description Globally - -To change the way filter types are named, you have to exchange the factory. - -You have to provide a delegate of the following type: - -```csharp -public delegate string GetFilterTypeDescription( - IDescriptorContext context, - Type entityType); -``` - -**Configuration** - -```csharp -descriptor.TypeName((context,types) => - context.Naming.GetTypeDescription(entityType, TypeKind.Object); + "Custom"); -``` - -**Result** - -```graphql -""" -Custom -""" -input UserFilter { - AND: [UserFilter!] - isOnline: Boolean - isOnline_not: Boolean - OR: [UserFilter!] -} -``` - -### Reset Configuration - -Hot Chocolate shippes with well-defined defaults. To start from scratch, you need to call `Reset()`first. - -**Configuration** - -```csharp -descriptor.Reset(); -``` - -**Result** - -> **⚠ Note:** You will need to add a complete configuration, otherwise the filter will not work as desired! - -## Describe with convention - -With the filter convention descriptor, you have full control over what filters are inferred, their names, operations, and a lot more. -The convention provides a familiar interface to the type configuration. We recommended to first take a look at `Filter & Operations` to understand the concept of filters. This will help you understand how the filter configuration works. - -Filtering has two core components at its heart. First, you have the inference of filters based on .NET types. The second part is an interceptor that translates the filters to the desired output and applies it to the resolver pipeline. These two parts can (and have to) be configured completely independently. With this separation, it is possible to easily extend the behavior. The descriptor is designed to be extendable by extension methods. - -**It's fluent** - -Filter conventions are a completely fluent experience. You can write a whole configuration as a chain of method calls. -This provides a very clean interface, but can, on the other hand, get messy quickly. We recommend using indentation to keep the configuration comprehensible. -You can drill up with `And()`. - -```csharp - descriptor.Operation(FilterOperationKind.Equals).Description("has to be equal"); - descriptor.Operation(FilterOperationKind.NotEquals).Description("has not to be equal"); - descriptor.Type(FilterKind.Comparable).Operation(FilterOperationKind.NotEquals).Description("has to be comparable and not equal") - - - descriptor - .Operation(FilterOperationKind.Equals) - .Description("has to be equal") - .And() - .Operation(FilterOperationKind.NotEquals) - .Description("has not to be equal") - .And() - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.NotEquals) - .Description("has to be comparable and not equal") -``` - -### Configuration of the type system - -In this section, we will focus on the generation of the schema. If you are interested in changing how filters translate to the database, you have to look here TODO:Link - -#### Configure Filter Operations - -There are two ways to configure Operations. - -You can configure a default configuration that applies to all operations of this kind. In this case the configuration for `FilterOperationKind.Equals` would be applied to all `FilterKind` that specify this operation. - -```csharp - descriptor.Operation(FilterOperationKind.Equals) -``` - -If you want to configure a more specific Operation e.g. `FilterOperationKind.Equal` of kind `FilterKind.String`, you can override the default behavior. - -```csharp - descriptor.Type(FilterKind.String).Operation(FilterOperationKind.Equals) -``` - -The operation descriptor allows you to configure the name, the description or even ignore an operation completely - -In this example, we will look at the following input type: - -```graphql -input UserFilter { - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - loggingCount_not: Int - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Change the name of an operation - -To change the name of an operation you need to specify a delegate of the following type: - -```csharp -public delegate NameString CreateFieldName( - FilterFieldDefintion definition, - FilterOperationKind kind); -``` - -**Configuration** - -```csharp {1, 6} - // (A) - // specifies that all not equals operations should be extended with _nada - descriptor - .Operation(FilterOperationKind.NotEquals) - .Name((def, kind) => def.Name + "_nada" ); - // (B) - // specifies that the not equals operations should be extended with _niente. - // this overrides (A) - descriptor - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.NotEquals) - .Name((def, kind) => def.Name + "_niente" ) -``` - -**result** - -```graphql {8,18} -input UserFilter { - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - loggingCount_niente: Int <-- (B) - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_nada: String <-- (A) - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Change the description of an operation - -In the same way, you can configure names you can also configure the description of operations. -You can either set the description for all operations of this kind or only for a specific one in combination with a filter kind. - -**Configuration** - -```csharp - descriptor - .Operation(FilterOperationKind.Equals) - .Description("has to be equal") - .And() - .Operation(FilterOperationKind.NotEquals) - .Description("has not to be equal") - .And() - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.NotEquals) - .Description("has to be comparable and not equal") -``` - -**result** - -```graphql {2-4,11-14, 20-22,27-29} -input UserFilter { - """ - has to be equal - """ - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - """ - has to be comparable and not equal - """ - loggingCount_not: Int - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - """ - has to be equal - """ - name: String - name_contains: String - name_ends_with: String - name_in: [String] - """ - has not to be equal - """ - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Hide Operations - -Hot Chocolate comes preconfigured with a set of operations. If you like to hide operations globally, you can use `Ignore` for it. -If your database provider does not support certain `IQueryable` methods you can just ignore the operation. Ignored operations do not generate filter input types. - -There are multiple ways to ignore an operation: - -**Configuration** - -```csharp - descriptor - .Ignore(FilterOperationKind.Equals) - .Operation(FilterOperationKind.NotEquals) - .Ignore() - .And() - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.GreaterThanOrEqual) - .Ignore(); -``` - -**result** - -```graphql {2,4, 8,14,18} -input UserFilter { - ↵ - loggingCount_gt: Int - ↵ - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - ↵ - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - ↵ - name_contains: String - name_ends_with: String - name_in: [String] - ↵ - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Configure Implicit Filter - -The default binding behavior of Hot Chocolate is implicit. Filter types are no exception. -This first may seem like magic, but unfortunately, there is none. It is just code. With `AddImplicitFilter` you can add this pinch of magic to your extension too. -Hot Chocolate creates the filters as it builds the input type. The type iterates over a list of factories sequentially and tries to create a definition for each property. The first factory that can handle the property wins and creates a definition for the filter. - -To configure you have to use the following delegate: - -```csharp - public delegate bool TryCreateImplicitFilter( - IDescriptorContext context, - Type type, - PropertyInfo property, - IFilterConvention filterConventions, - [NotNullWhen(true)] out FilterFieldDefintion? definition); -``` - -| parameter | type | description | -| ------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------- | -| _context_ | `IDescriptorContext` | The context of the type descriptor | -| _type_ | `Type` | The type of the property. `Nullable` is already unwrapped (typeof(T)) | -| _property_ | `PropertyInfo` | The property | -| _filterConventions_ | `IFilterConvention` | The instance of the `IFilterContention`. | -| _definition_ | `out FilterFieldDefintion?` | The generated definition for the property. Return null if the current factory cannot handle the property. | - -If you just want to build your extension for implicit bindings, you can just out a custom `FilterFieldDefinition`. - -It makes sense to encapsulate that logic in a FilterFieldDescriptor though. You can reuse this descriptor also for the fluent configuration interface. - -**Example** - -```csharp -private static bool TryCreateStringFilter( - IDescriptorContext context, - Type type, - PropertyInfo property, - IFilterConvention filterConventions, - [NotNullWhen(true)] out FilterFieldDefintion? definition) -{ - if (type == typeof(string)) - { - var field = new StringFilterFieldDescriptor(context, property, filterConventions); - definition = field.CreateDefinition(); - return true; - } - - definition = null; - return false; -} -``` - -##### Creating a fluent filter extension - -Hot Chocolate provides fluent interfaces for all its APIs. If you want to create an extension that integrates seamlessly with Hot Chocolate it makes sense to also provide fluent interfaces. It makes sense to briefly understand how `Type -> Descriptor -> Definition` work. You can read more about it here //TODO LINK - -Here a quick introduction: - -_Type_ - -A type is a description of a GraphQL Type System Object. Hot Chocolate builds types during schema creation. Types specify how a GraphQL Type looks like. It holds, for example, the definition, fields, interfaces, and all life cycle methods. Type do only exist on startup; they do not exist on runtime. - -_Type Definition_ - -Each type has a definition that describes the type. It holds, for example, the name, description, the CLR type and the field definitions. The field definitions describe the fields that are on the type. - -_Type Descriptor_ - -A type descriptor is a fluent interface to describe the type over the definition. The type descriptor does not have access to the type itself. It operates solely on the definition. - -In the case of filtering, this works nearly the same. The `FilterInputType` is just an extension of the `InputObjectType`. It also has the same _Definition_. The `FilterInputType` stores `FilterOperationField` on this definition. These are extensions of the normal `InputField`'s and extend it by a `FilterOperationKind`. - -With a normal `InputTypeDescriptor` you declare a field by selecting a member. The filter descriptor works a little differently. You declare the `FilterKind` of a member by selecting it and then you declare the operations on this filter. These operations are the input field configuration. - -```csharp -InputTypeDescriptor inputDesc; -inputDesc.Field(x => x.Name) - .Description("This is the name") - - -FilterInputTypeDescriptor inputDesc; -inputDesc.Filter(x => x.Name).AllowEqual().Description("This is the name") -``` - -We have a few case studies that will show you how you can change the inference: - -1. String "\_like" shows an example of how you can easily add a "\_like" operation to the string filter -2. DateTime "from", "to" -3. NetTopologySuite - -> The configuration you see in this case study only shows how you add an operation to an already-existing filter. After this, the job is only half way done. To create a working filter, you must also change the expression visitor. Check the documentation for //TODO: ExpressionVisitor - -##### Case Study: String "\_like" - -**Situation** -The customer has requested a full-text search of the description field of a product. The product owner has promised the feature to the customer two sprints ago and it has still not been shipped. The UX guru of your company has, slightly under pressure, worked out a solution, and together with the frontend team they have already build a prototype. In the heat of the moment, they did not read the user story correctly and, unfortunately, realized last minute that the current filtering API does not fit their needs. The customer does also has to be able to create complex search queries. `This%Test` should match `This is a Test`. As you come back from lunch a hysterical product owner explains the situation to you. To you, it is immediately clear that this can be easily done by using the SQL `like` operator. - -In your codebase you use the `UseFiltering` middleware extensively. In some cases, you also have customized filter types. To cover all possible cases you need - -1. Implicit Binding: `[UseFiltering]` should automagically create the "\_like" filter for every string filter -2. Explicity Binding: `desc.Filter(x => x.Description).AllowLike())` -3. Expression Visitor: You want to directly filter on the database. You use EF Core. - -**Implicit Binding** -With the conventions, it is easy to add operations on already existing filters. We will first look into the configuration for filter inference and in a second step into the code first extension. - -You just need to navigate to the filter you like to modify. `descriptor.Type(FilterKind.String)`. Just add the operation you need with `.Operation(FilterOperationKind.Like)`. The next step is to add factories for the name and the description. - -Altogether this looks like this: - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - descriptor - .Type(FilterKind.String) - .Operation(FilterOperationKind.GreaterThanOrEqual) - .Name((def, kind) => def.Name + "_like" ); - .Description("Full text search. Use % as a placeholder for any symbol"); - } -} -``` - -**Explicit Binding** -By extending the filter descriptor of the string filter you can add a fluent extension that seamlessly integrated with the Hot Chocolate API. - -//TODO: currently there `StringFilterOperationDescriptor` requires `StringFilterFieldDescriptor` instead of `StringFilterFieldDescriptor` and there is no way to `Allow` -//TODO: TYPO ! FilterFieldDefintion -//TODO: Move RewriteType to convention . -//TODO: Move up CreateFieldName - -```csharp -public static class StringLikeFilterExtension -{ - public static IStringFilterOperationDescriptor AllowLike( - IStringFilterFieldDescriptor descriptor) - { - return descriptor.Allow( - FilterOperationKind.ArrayAll, - (ctx, definition) => - { - var operation = new FilterOperation( - typeof(string), FilterOperationKind.ArrayAll, definition.Property); - - return StringFilterOperationDescriptor.New( - ctx, - descriptor, - ctx.GetFilterConvention().CreateFieldName(FilterOperationKind.ArrayAll), - ctx.GetFilterConvention().RewriteType(FilterOperationKind.ArrayAll), - operation); - } - ) - } -} -``` - ---- - -##### Case Study: DateTime "from", "to" - -**Situation** - -1. Implicit Binding: `[UseFiltering]` should automagically create `DateTimeFilter` and the corresponding "\_from" and "\_to". -2. Explicity Binding: `desc.Filter(x => x.OrderedAt).AllowFrom().AllowTo())` -3. Expression Visitor: You want to directly filter on the database. You use EF Core. - -**Configuration** - -It is slightly more complex to create a custom filter than just modifying existing operations. There are a few different parts that must come together to make this work. Implicit and Explicit Bindings are coming together in this example. - -Let's start with the configuration of the convention. By splitting the configuration up into a set of extension methods that can be applied to the convention, it is possible to easily replace sub-components of the extension. e.g. some users might want to use an expression visitor, some others might want to use MognoDB Native. - -- `UseDateTimeFilter` adds support for date-time filters and registers the expression visitor for it. Abstraction for `UseDateTimeFilterImplicitly().UseDateTimeExpression()` - -- `UseDateTimeFilterImplicitly` only registers the configuration of the schema building part of the extension - -- `UseDateTimeExpression` only registers the expression visitor configuration. - -With this separation, a user that prefers to use a custom visitor, can just register the types and skip the expression visitor configuration - -TODO: UseExpressionVisitor should return expression visitor if it already exists -TODO: Reference Definition from Filter Operation instead of property. This way we could reduce complexity further and improve extensibility - -```csharp -public static class DateTimeFilterConventionExtensions -{ - public static IFilterConventionDescriptor UseDateTimeFilter( - this IFilterConventionDescriptor descriptor) => - descriptor.UseDateTimeFilterImplicitly() - .UseDateTimeFilterExpression(); - - public static IFilterConventionDescriptor UseDateTimeFilterImplicitly( - this IFilterConventionDescriptor descriptor) => - descriptor.AddImplicitFilter(TryCreateDateTimeFilter) - .Type(FilterKind.DateTime) - .Operation(FilterOperationKind.GreaterThanOrEquals) - .Name((def, _) => def.Name + "_from") - .Description("") - .And() - .Operation(FilterOperationKind.LowerThanOrEquals) - .Name((def, _) => def.Name + "_to") - .Description("") - .And() - .And(); - - public static IFilterConventionDescriptor UseDateTimeFilterExpression( - this IFilterConventionDescriptor descriptor) => - descriptor.UseExpressionVisitor() - .Kind(FilterKind.DateTime) - .Operation(FilterOperationKind.LowerThanOrEquals) - .Handler(ComparableOperationHandlers.LowerThanOrEquals).And() - .Operation(FilterOperationKind.GreaterThanOrEquals) - .Handler(ComparableOperationHandlers.GreaterThanOrEquals).And() - .And() - .And(); -} -``` - -**Create Date Time Filter Implicitly** - -`DateTime` is a new filter. Hot Chocolate is only aware of its existence because of the delegate passed to `AddImplicitFilter` - -```csharp -private static bool TryCreateDateTimeFilter( - IDescriptorContext context, - Type type, - PropertyInfo property, - IFilterConvention filterConventions, - [NotNullWhen(true)] out FilterFieldDefintion? definition) -{ - if (type == typeof(DateTime)) - { - var field = new DateTimeFilterFieldDescriptor( - context, property, filterConventions); - definition = field.CreateDefinition(); - return true; - } - - definition = null; - return false; -} -``` - -TODO: make filters name based -**Filter Field** - -A filter field is a collection of operations. It holds the configuration of the different operations like _“from”_ and _“to”_. In classic Hot Chocolate fashion there is a descriptor that describes these collections. Hot Chocolate provides the base class `FilterFieldDescriptorBase` you can use as an extension point. There is quite a lot of boilerplate code you need to write. e.g. it makes sense to define an interface for the descriptor. -You find an example here: //TODO LINK - -For the explicit binding, we need to override `CreateOperationDefinition`. In case the filter is bound implicitly, this method is invoked for each operation. -TODO: I think there is an issue with AllowNotEndsWith. - -```csharp -// We override this method for implicity binding -protected override FilterOperationDefintion CreateOperationDefinition( - FilterOperationKind operationKind) => - CreateOperation(operationKind).CreateDefinition(); -``` - -For the implicit binding, we only need to add the methods `AllowFrom` and `AllowTo`. - -```csharp -// The following to methods are for adding the filters explicitly -public IDateTimeFilterOperationDescriptor AllowFrom() => - GetOrCreateOperation(FilterOperationKind.GreaterThanOrEqual); - -public IDateTimeFilterOperationDescriptor AllowTo() => - GetOrCreateOperation(FilterOperationKind.LowerThanOrEqual); - -// This is just a little helper that reduces code duplication -private DateTimeFilterOperationDescriptor GetOrCreateOperation( - FilterOperationKind operationKind) => - Filters.GetOrAddOperation(operationKind, - () => CreateOperation(operationKind)); -``` - -All the methods described above call `CreateOperation`. This method creates the operation descriptor. The `FitlerOperation` that is created here, will also be available for the expression visitor. - -```csharp -// This helper method creates the operation. -private DateTimeFilterOperationDescriptor CreateOperation( - FilterOperationKind operationKind) - { - // This operation is also available in execution. - var operation = new FilterOperation( - typeof(DateTime), - Definition.Kind, - operationKind, - Definition.Property); - - return DateTimeOffsetFilterOperationDescriptor.New( - Context, - this, - CreateFieldName(operationKind), - RewriteType(operationKind), - operation, - FilterConvention); - } -``` - -**Filter Operation** - -In this example; there are two filter operations _"form"_ and _"to"_. The configuration with a descriptor combines explicit and implicit binding. As a base class, you can use `FilterOperationDescriptorBase`. -Here is the interface that is used in this example: - -```csharp -public interface IDateTimeFilterOperationDescriptor - : IDescriptor - , IFluent - { - /// Define filter operations for another field. - IDateTimeFilterFieldDescriptor And(); - - /// Specify the name of the filter operation. - IDateTimeFilterOperationDescriptor Name(NameString value); - - /// Specify the description of the filter operation. - IDateTimeFilterOperationDescriptor Description(string value); - - /// Annotate the operation filter field with a directive. - IDateTimeFilterOperationDescriptor Directive(T directiveInstance) - where T : class; - IDateTimeFilterOperationDescriptor Directive() - where T : class, new(); - IDateTimeFilterOperationDescriptor Directive( - NameString name, - params ArgumentNode[] arguments); - } -``` - -You can find the implementation of this interface here: //TODO link - -**Filter Type Extension** -The last missing piece to complete the integration into Hot Chocolate is an extension of `FilterInputType`. This can again be done as a extension method. - -```csharp -public IStringFilterFieldDescriptor Filter( - Expression> property) -{ - if (property.ExtractMember() is PropertyInfo p) - { - return Fields.GetOrAddDescriptor(p, - () => new StringFilterFieldDescriptor(Context, p)); - } - - throw new ArgumentException( - FilterResources.FilterInputTypeDescriptor_OnlyProperties, - nameof(property)); -} -``` - -//TODO Open this api - ---- - -##### Case Study: Filters for NetTopologySuite - -**Situation** - -> **Note:** If you are searching for `NetTopologySuite`, they are already implemented. Have a look at//TODO LINK - -1. Implicit Binding: `[UseFiltering]` should automagically create `Point` and the corresponding "\_distance" -2. Explicity Binding: `desc.Filter(x => x.Location).AllowDistance()` -3. Expression Visitor: You want to directly filter on the database. You use EF Core. - -Things are different in this case, as there is no longer a 1:1 mapping of input type to method or property. Imagine you want to fetch all bakeries that are near you. In C# you would write something like `dbContext.Bakeries.Where(x => x.Location.Distance(me.Location) < 5)`. This cannot be translated to a _GraphQL_ input type directly. - -A _GraphQL_ query might look like this. - -```graphql -{ - bakeries( - where: { location: { distance: { from: { x: 32, y: 15 }, is_lt: 5 } } } - ) { - name - } -} -``` - -_GraphQL_ input fields cannot have arguments. To work around this issue a data structure is needed that combines the filter payload and the operation. The input type for this example has the following structure. - -```csharp -public class FilterDistance -{ - - public FilterDistance( - FilterPointData from) - { - From = from; - } - /// contains the x and y coordinates. - public FilterPointData From { get; } - - public double Is { get; set; } -} -``` - -```graphql -input FilterDistanceInput { - from: FilterPointDataInput! - is: Float - is_gt: Float - is_gte: Float - is_lt: Float - is_lte: Float - is_in: Float - is_not: Float - is_not_gt: Float - is_not_gte: Float - is_not_lt: Float - is_not_lte: Float - is_not_in: Float -} -``` - -//TODO: Add skip / inopfield! - -Hot Chocolate would generate nested filters for the payload property "From" by default. This can be avoided by declaring the field as input payload. - -```csharp -public class DistanceFilterType - : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.Input(x => x.From); - descriptor.Filter(x => x.Is); - } -} -``` - -**Convention & Implicit Factory & Type Descriptor** - -The configuration of the convention, the implicit type factory and the descirptors are very similar to the the two examples before. To not bloat the documentation with duplication we just refere to these two examples and to the reference implementation here //TODO LINK - ---- - -## Translating Filters - -Hot Chocolate can translate incoming filters requests directly onto collections or even on to the database. In the default implementation, the output of this translation is a Linq expression that can be applied to `IQueryable` and `IEnumerable`. You can choose to change the expression that is generated or can even create custom output. Hot Chocolate is using visitors to translate input objects. - -[Learn more about visitors here](/docs/hotchocolate/v12/api-reference/visitors). - -### Expression Filters - -Filter conventions make it easier to change how an expression should be generated. There are three different extension points you can use to change the behavior of the expression visitor. You do not have to worry about the visiting of the input object itself. - -#### Describe the Expression Visitor - -The expression visitor descriptor is accessible through the filter convention. By calling `UseExpressionVisitor` on the convention descriptor you gain access. The expression visitor has the default set of expressions preconfigured. - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor) - { - descriptor.UseExpressionVisitor() - } -} -``` - -The descriptor provides a fluent interface that is very similar to the one of the convention descriptor itself. You have to specify what _operation_ on which _filter kind_ you want to configure. You can drill with `Kind` and `Operation` and go back up by calling `And()`: - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor) - { - descriptor - .UseExpressionVisitor() - .Kind(FilterKind.String) - .Operation(FilterKind.Equals) - .And() - .And() - .Kind(FilterKind.Comparable) - .Operation(FilterKind.In) - } -} -``` - -**Visitation Flow** - -The expression visitor starts as any other visitor at the node you pass in. Usually, this is the node object value node of the filter argument. It then starts the visitation. Every time the visitor _enters_ or _leaves_ an object field, it looks for a matching configuration. If there is no special _enter_ behavior of a field, the visitor generates the expression for the combination of _kind_ and _operation_. - -The next two paragraphs show how the algorithm works in detail. - -_Enter_ - -On _entering_ a field, the visitor tries to get a `FilterFieldEnter` delegate for the `FilterKind` of the current field. If a delegate was found, executed, and the execution return true, the `Enter` method returns the _action_ specified by the delegate. In all other cases, the visitor tries to execute an `OperationHandler` for the combination `FilterKind` and `OperationKind`. If the handler returns true, the expression returned by the handler is added to the context. - -1. Let _field_ be the field that is visited -1. Let _kind_ be the `FilterKind` of _field_ -1. Let _operation_ be the `FilterOperationKind` of _field_ -1. Let _convention_ be the `FilterConvention` used by this visitor -1. Let _enterField_ be the `FilterFieldEnter` delegate for _kind_ on _convention_ -1. If _enterField_ is not null: - 1. Let _action_ be the visitor action of _enterField_ - 1. If _enterField_ returns true: - 1. **return** _action_ -1. Let _operationHander_ be the `FilterOperationHandler` delegate for (_kind_, _operation_) on _convention_ -1. If _operationHandler_ is not null: - 1. Let _expression_ be the expression generated by _operationHandler_ - 1. If _enterField_ returns true: - 1. enqueue _expression_ -1. **return** `SkipAndLeave` - -_Leave_ - -On _entering_ a field, the visitor tries to get and execute a `FilterFieldLeave` delegate for the `FilterKind` of the current field. - -1. Let _field_ be the field that is visited -1. Let _kind_ be the `FilterKind` of _field_ -1. Let _operation_ be the `FilterOperationKind` of _field_ -1. Let _convention_ be the `FilterConvention` used by this visitor -1. Let _leaveField_ be the `FilterFieldLeave` delegate for _kind_ on _convention_ -1. If _leaveField_ is not null: - 1. Execute _leaveField_ - -**Operations** - -The operation descriptor provides you with the method `Handler`. With this method, you can configure, how the expression for the _operation_ of this filter _kind_ is generated. You have to pass a delegate of the following type: - -```csharp -public delegate bool FilterOperationHandler( - FilterOperation operation, - IInputType type, - IValueNode value, - IQueryableFilterVisitorContext context, - [NotNullWhen(true)]out Expression? result); -``` - -This delegate might seem intimidating first, but it is not bad as it looks. If this delegate `true` the `out Expression?` is enqueued on the filters. This means that the visitor will pick it up as it composes the filters. - -| Parameter | Description | -| ---------------------------------------- | --------------------------------------- | -| `FilterOperation operation` | The operation of the current field | -| `IInputType type` | The input type of the current field | -| `IValueNode value` | The AST value node of the current field | -| `IQueryableFilterVisitorContext context` | The context that builds up the state | -| `out Expression? result` | The generated expression | - -Operations handlers can be configured like the following: - -```csharp {10,13} -public class CustomConvention : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor) - { - descriptor - .UseExpressionVisitor() - .Kind(FilterKind.String) - .Operation(FilterKind.Equals) - .Handler(YourVeryOwnHandler.HandleEquals) - .And() - .Operation(FilterKind.NotEquals) - .Handler(YourVeryOwnHandler.HandleNotEquals) - } -} -``` - -TODO: add example - -**Kind** - -There are two extension points on each _filter kind_. You can alter the _entering_ of a filter and the _leaving_. - -**Enter** -You can configure the entering with the following delegate: - -```csharp -public delegate bool FilterFieldEnter( - FilterOperationField field, - ObjectFieldNode node, - IQueryableFilterVisitorContext context, - [NotNullWhen(true)]out ISyntaxVisitorAction? action); -``` - -If this field returns _true_ the filter visitor will continue visitation with the specified _action_ in the out parameter `action`. [Check out the visitor documentation for all possible actions](http://addlinkshere). -If the field does not return true and a visitor action, the visitor will continue and search for a _operation handler_. After this, the visitor will continue with `SkipAndLeave`. - -| Parameter | Description | -| ---------------------------------------- | ------------------------------------ | -| `FilterOperationField field` | The current field | -| `ObjectFieldNode node` | The object node of the current field | -| `IQueryableFilterVisitorContext context` | The context that builds up the state | -| `out ISyntaxVisitorAction? action` | The visitor action | - -**Leave** -You can configure the entering with the following delegate: - -```csharp -public delegate void FilterFieldLeave( - FilterOperationField field, - ObjectFieldNode node, - IQueryableFilterVisitorContext context); -``` - -| Parameter | Description | -| ---------------------------------------- | ------------------------------------ | -| `FilterOperationField field` | The current field | -| `ObjectFieldNode node` | The object node of the current field | -| `IQueryableFilterVisitorContext context` | The context that builds up the state | diff --git a/website/src/docs/hotchocolate/v12/api-reference/language.md b/website/src/docs/hotchocolate/v12/api-reference/language.md index c10b44dcf0a..42359107510 100644 --- a/website/src/docs/hotchocolate/v12/api-reference/language.md +++ b/website/src/docs/hotchocolate/v12/api-reference/language.md @@ -53,7 +53,7 @@ This interface defines the `NodeKind` of the node. | Variable | [A variable](https://spec.graphql.org/June2018/#sec-Language.Variables) | Query (out) | \$foo | | SelectionSet | [specifies a selection of _Field_, _FragmentSpread_ or _InlineFragment_](https://spec.graphql.org/June2018/#sec-Selection-Sets) | Query (out) | {foo bar} | | Field | [Describes a field as a part of a selection set](https://spec.graphql.org/June2018/#sec-Language.Fields) | Query (out) | foo | -| FragmentSpread | [Denotes a spread of a `FragemntDefinition`](https://spec.graphql.org/June2018/#FragmentSpread) | Query (out) | ...f1 | +| FragmentSpread | [Denotes a spread of a `FragmentDefinition`](https://spec.graphql.org/June2018/#FragmentSpread) | Query (out) | ...f1 | | InlineFragment | [Denotes an inline fragment](https://spec.graphql.org/June2018/#sec-Inline-Fragments) | Query (out) | ... on Foo { bar} | | FragmentDefinition | [Defines the definition of a fragment](https://spec.graphql.org/June2018/#FragmentDefinition) | Query (out) | fragment f1 on Foo {} | | IntValue | [Denotes a `int` value](https://spec.graphql.org/June2018/#sec-Int-Value) | Query (in) | 1 | diff --git a/website/src/docs/hotchocolate/v12/api-reference/migrate-from-11-to-12.md b/website/src/docs/hotchocolate/v12/api-reference/migrate-from-11-to-12.md index 1f4cafb155a..f74be5b4189 100644 --- a/website/src/docs/hotchocolate/v12/api-reference/migrate-from-11-to-12.md +++ b/website/src/docs/hotchocolate/v12/api-reference/migrate-from-11-to-12.md @@ -222,7 +222,7 @@ services **v12** ```csharp -sevices +services .AddGraphQL() .AddQueryFieldToMutationPayloads(options => { diff --git a/website/src/docs/hotchocolate/v12/api-reference/visitors.md b/website/src/docs/hotchocolate/v12/api-reference/visitors.md index 5df5d9b8bfb..f42676966ed 100644 --- a/website/src/docs/hotchocolate/v12/api-reference/visitors.md +++ b/website/src/docs/hotchocolate/v12/api-reference/visitors.md @@ -167,3 +167,5 @@ query { } } ``` + + diff --git a/website/src/docs/hotchocolate/v12/defining-a-schema/directives.md b/website/src/docs/hotchocolate/v12/defining-a-schema/directives.md index 877cdcd4c60..bb0fa4bf568 100644 --- a/website/src/docs/hotchocolate/v12/defining-a-schema/directives.md +++ b/website/src/docs/hotchocolate/v12/defining-a-schema/directives.md @@ -408,3 +408,5 @@ If there were more directives in the query, they would be appended to the direct So, now the order would be like the following: `a, b, c, d, e, f`. Every middleware can execute the original resolver function by calling `ResolveAsync()` on the `IDirectiveContext`. + + diff --git a/website/src/docs/hotchocolate/v12/defining-a-schema/scalars.md b/website/src/docs/hotchocolate/v12/defining-a-schema/scalars.md index 9df27a888d5..939031f1291 100644 --- a/website/src/docs/hotchocolate/v12/defining-a-schema/scalars.md +++ b/website/src/docs/hotchocolate/v12/defining-a-schema/scalars.md @@ -2,7 +2,7 @@ title: "Scalars" --- -Scalar types are the primitives of our schema and can hold a specific type of data. They are leaf types, meaning we cannot use e.g. `{ fieldname }` to further drill down into the type. The main purpose of a scalar is to define how a value is serialized and deserialized. +Scalar types are the primitives of our schema and can hold a specific type of data. They are leaf types, meaning we cannot use e.g. `{ fieldName }` to further drill down into the type. The main purpose of a scalar is to define how a value is serialized and deserialized. Besides basic scalars like `String` and `Int`, we can also create custom scalars like `CreditCardNumber` or `SocialSecurityNumber`. These custom scalars can greatly enhance the expressiveness of our schema and help new developers to get a grasp of our API. diff --git a/website/src/docs/hotchocolate/v12/distributed-schema/index.md b/website/src/docs/hotchocolate/v12/distributed-schema/index.md index 3379f49da59..96cc3f70963 100644 --- a/website/src/docs/hotchocolate/v12/distributed-schema/index.md +++ b/website/src/docs/hotchocolate/v12/distributed-schema/index.md @@ -32,7 +32,7 @@ _Schema of the Address Service_ ```sdl type Query { - addressOfPerson(persondId: ID!): Address + addressOfPerson(personId: ID!): Address } type Address { diff --git a/website/src/docs/hotchocolate/v12/distributed-schema/schema-configuration.md b/website/src/docs/hotchocolate/v12/distributed-schema/schema-configuration.md index 42cad53df2b..6c80e7cf28a 100644 --- a/website/src/docs/hotchocolate/v12/distributed-schema/schema-configuration.md +++ b/website/src/docs/hotchocolate/v12/distributed-schema/schema-configuration.md @@ -67,7 +67,7 @@ In schema stitching type renames can be defined on the gateway: services .AddGraphQLServer() .AddRemoteSchema(Products) - .AddRemoteSchema(Inventiory) + .AddRemoteSchema(Inventory) .RenameType("Category","ProductCategory", Products); ``` @@ -136,7 +136,7 @@ In schema stitching field renames can be defined on the gateway: services .AddGraphQLServer() .AddRemoteSchema(Products) - .AddRemoteSchema(Inventiory) + .AddRemoteSchema(Inventory) .RenameField("Query", "categories", "productCategories", schemaName: Products) ``` @@ -182,7 +182,7 @@ If you want to remove a specific type from the schema you can also use `IgnoreTy services .AddGraphQLServer() .AddRemoteSchema(Products) - .AddRemoteSchema(Inventiory) + .AddRemoteSchema(Inventory) .IgnoreType("Category", schemaName: Products); ``` @@ -215,7 +215,7 @@ This can be useful when you want to merge root fields of domain services, but ig services .AddGraphQLServer() .AddRemoteSchema(Products) - .AddRemoteSchema(Inventiory) + .AddRemoteSchema(Inventory) .IgnoreField("Query", "categories", Products) .IgnoreField("Query", "categories", Inventory); ``` @@ -252,7 +252,7 @@ type InventoryInfo { type Query { inventoryInfo(upc: Int!): InventoryInfo! - shippingEsitmate(price: Int!, weight: Int!): InventoryInfo! + shippingEstimate(price: Int!, weight: Int!): InventoryInfo! } ``` @@ -453,7 +453,7 @@ It would be better if the middleware is only applied to the field that needs it. You can use a schema interceptor to apply the middleware to the fields that use it. ```csharp -public class MessageMiddlwareInterceptor : TypeInterceptor +public class MessageMiddlewareInterceptor : TypeInterceptor { public override bool CanHandle(ITypeSystemObjectContext context) { @@ -524,7 +524,7 @@ services .PublishSchemaDefinition( c => c .SetName("inventory") - // Ignores the root types. This removes `inStock` and `shippingEsitmate` + // Ignores the root types. This removes `inStock` and `shippingEstimate` // from the `Query` type of the Gateway .IgnoreRootTypes() // Adds a type extension. diff --git a/website/src/docs/hotchocolate/v12/distributed-schema/schema-federations.md b/website/src/docs/hotchocolate/v12/distributed-schema/schema-federations.md index 6a3af847500..40f44854396 100644 --- a/website/src/docs/hotchocolate/v12/distributed-schema/schema-federations.md +++ b/website/src/docs/hotchocolate/v12/distributed-schema/schema-federations.md @@ -21,7 +21,7 @@ You will need to add a package reference to `HotChocolate.Stitching.Redis` to al A domain service has to _publish the schema definition_. The schema is published on the initialization of the schema. By default, a schema is lazy and only initialized when the first request is sent. -You can also initialize the schema on startup with `IntitializeOnStartup`. +You can also initialize the schema on startup with `InitializeOnStartup`. Every schema requires a unique name. This name is used in several places to reference the schema. By calling `PublishSchemaDefinition` you can configure how the schema should be published. diff --git a/website/src/docs/hotchocolate/v12/execution-engine/field-middleware.md b/website/src/docs/hotchocolate/v12/execution-engine/field-middleware.md index c699881f16c..2e075aa3879 100644 --- a/website/src/docs/hotchocolate/v12/execution-engine/field-middleware.md +++ b/website/src/docs/hotchocolate/v12/execution-engine/field-middleware.md @@ -258,7 +258,7 @@ descriptor await next(context); // It only makes sense to access the result after calling - // next(context), i.e. after the field resovler and any later + // next(context), i.e. after the field resolver and any later // middleware has finished executing. object? result = context.Result; diff --git a/website/src/docs/hotchocolate/v12/fetching-data/fetching-from-rest.md b/website/src/docs/hotchocolate/v12/fetching-data/fetching-from-rest.md index 592efea1eef..efe3cdc4f19 100644 --- a/website/src/docs/hotchocolate/v12/fetching-data/fetching-from-rest.md +++ b/website/src/docs/hotchocolate/v12/fetching-data/fetching-from-rest.md @@ -251,3 +251,5 @@ You can now head over to your Banana Cake Pop on your GraphQL Server (/graphql) } } ``` + + diff --git a/website/src/docs/hotchocolate/v12/fetching-data/filtering.md b/website/src/docs/hotchocolate/v12/fetching-data/filtering.md index caacb2cb508..9c8d453510e 100644 --- a/website/src/docs/hotchocolate/v12/fetching-data/filtering.md +++ b/website/src/docs/hotchocolate/v12/fetching-data/filtering.md @@ -297,7 +297,7 @@ input UserFilterInput { ## Comparable Filter -Defines filters for `IComparables` like: `bool`, `byte`, `shot`, `int`, `long`, `float`, `double` `decimal`, `Guid`, `DateTime`, `DateTimeOffset` and `TimeSpan` +Defines filters for `IComparable`s like: `bool`, `byte`, `shot`, `int`, `long`, `float`, `double` `decimal`, `Guid`, `DateTime`, `DateTimeOffset` and `TimeSpan` ```csharp public class User diff --git a/website/src/docs/hotchocolate/v12/fetching-data/pagination.md b/website/src/docs/hotchocolate/v12/fetching-data/pagination.md index 9bfcd2a7203..97285afc849 100644 --- a/website/src/docs/hotchocolate/v12/fetching-data/pagination.md +++ b/website/src/docs/hotchocolate/v12/fetching-data/pagination.md @@ -51,7 +51,7 @@ Adding pagination capabilities to our fields is a breeze. All we have to do is a public class Query { [UsePaging] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) => repository.GetUsers(); } ``` @@ -69,7 +69,7 @@ public class QueryType : ObjectType .UsePaging() .Resolve(context => { - var repository = context.Service(); + var repository = context.Service(); return repository.GetUsers(); }); @@ -88,7 +88,7 @@ To make our life easier, we do not have to write out the _Connection_ types in o public class Query { [UsePaging] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) => repository.GetUsers(); } @@ -130,7 +130,7 @@ We can also specify a custom name for our _Connection_ like the following. public class Query { [UsePaging(ConnectionName = "CustomUsers")] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) { // Omitted code for brevity } @@ -466,7 +466,7 @@ To add _offset-based_ pagination capabilities to our fields we have to add the ` public class Query { [UseOffsetPaging] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) => repository.GetUsers(); } ``` @@ -484,7 +484,7 @@ public class QueryType : ObjectType .UseOffsetPaging() .Resolve(context => { - var repository = context.Service(); + var repository = context.Service(); return repository.GetUsers(); }); diff --git a/website/src/docs/hotchocolate/v12/integrations/mongodb.md b/website/src/docs/hotchocolate/v12/integrations/mongodb.md index b5d75f9e6b0..436f55d2eb7 100644 --- a/website/src/docs/hotchocolate/v12/integrations/mongodb.md +++ b/website/src/docs/hotchocolate/v12/integrations/mongodb.md @@ -261,3 +261,5 @@ public IExecutable GetPersonById( return collection.Find(x => x.Id == id).AsExecutable(); } ``` + + diff --git a/website/src/docs/hotchocolate/v12/integrations/neo4j.md b/website/src/docs/hotchocolate/v12/integrations/neo4j.md index a683d338d4f..7ff1331b506 100644 --- a/website/src/docs/hotchocolate/v12/integrations/neo4j.md +++ b/website/src/docs/hotchocolate/v12/integrations/neo4j.md @@ -219,3 +219,5 @@ query GetPersons { } } ``` + + diff --git a/website/src/docs/hotchocolate/v12/integrations/spatial-data.md b/website/src/docs/hotchocolate/v12/integrations/spatial-data.md index a960fc7c2eb..fa80a6fdb93 100644 --- a/website/src/docs/hotchocolate/v12/integrations/spatial-data.md +++ b/website/src/docs/hotchocolate/v12/integrations/spatial-data.md @@ -161,7 +161,7 @@ interface GeoJSONInterface { } ``` -A `NetTopologySuite.Gemeotry` is mapped to this interface by default. +A `NetTopologySuite.Geometry` is mapped to this interface by default. ## Input Types @@ -602,3 +602,5 @@ Additionally we want to provide a way for users, to specify in what CRS they wan Currently we only support filtering for spatial data. We also want to provide a way for users to sort results. This can e.g. be used to find the nearest result for a given point. + + diff --git a/website/src/docs/hotchocolate/v12/performance/automatic-persisted-queries.md b/website/src/docs/hotchocolate/v12/performance/automatic-persisted-queries.md index 75587efe9e8..f57c8ff329f 100644 --- a/website/src/docs/hotchocolate/v12/performance/automatic-persisted-queries.md +++ b/website/src/docs/hotchocolate/v12/performance/automatic-persisted-queries.md @@ -293,3 +293,5 @@ curl -g 'http://localhost:5000/graphql/?extensions={"persistedQuery":{"version": ```json { "data": { "__typename": "Query" } } ``` + + diff --git a/website/src/docs/hotchocolate/v12/server/files.md b/website/src/docs/hotchocolate/v12/server/files.md index 9ede542a0c0..e7c5fbfa2bc 100644 --- a/website/src/docs/hotchocolate/v12/server/files.md +++ b/website/src/docs/hotchocolate/v12/server/files.md @@ -215,7 +215,7 @@ public class Mutation // If the user is allowed to upload the profile picture // we generate the token - var token = "myuploadtoken"; + var token = "myUploadToken"; var uploadUrl = QueryHelpers.AddQueryString(baseUrl, "token", token); @@ -250,7 +250,7 @@ mutation { { "data": { "uploadProfilePicture": { - "uploadUrl": "https://blob.chillicream.com/upload?token=myuploadtoken" + "uploadUrl": "https://blob.chillicream.com/upload?token=myUploadToken" } } } diff --git a/website/src/docs/hotchocolate/v12/server/interceptors.md b/website/src/docs/hotchocolate/v12/server/interceptors.md index c60ad102eba..45aa3ad3a8a 100644 --- a/website/src/docs/hotchocolate/v12/server/interceptors.md +++ b/website/src/docs/hotchocolate/v12/server/interceptors.md @@ -47,7 +47,7 @@ public override ValueTask OnCreateAsync(HttpContext context, } ``` -> Warning: `base.OnCreateAsync` should always be invoked, since the default implementation takes care of adding the dependency injection services as well as some important global state variables, such as the `ClaimsPrinicpal`. Not doing this can lead to unexpected issues. +> Warning: `base.OnCreateAsync` should always be invoked, since the default implementation takes care of adding the dependency injection services as well as some important global state variables, such as the `ClaimsPrincipal`. Not doing this can lead to unexpected issues. Most of the configuration will be done through the `OperationRequestBuilder`, injected as argument to this method. @@ -145,7 +145,7 @@ public override ValueTask OnRequestAsync(ISocketConnection connection, } ``` -> Warning: `base.OnRequestAsync` should always be invoked, since the default implementation takes care of adding the dependency injection services as well as some important global state variables, such as the `ClaimsPrinicpal`. Not doing this can lead to unexpected issues. +> Warning: `base.OnRequestAsync` should always be invoked, since the default implementation takes care of adding the dependency injection services as well as some important global state variables, such as the `ClaimsPrincipal`. Not doing this can lead to unexpected issues. Most of the configuration will be done through the `OperationRequestBuilder`, injected as argument to this method. diff --git a/website/src/docs/hotchocolate/v13/api-reference/apollo-federation.md b/website/src/docs/hotchocolate/v13/api-reference/apollo-federation.md index 9f8c9c692ad..3a2e6c01d90 100644 --- a/website/src/docs/hotchocolate/v13/api-reference/apollo-federation.md +++ b/website/src/docs/hotchocolate/v13/api-reference/apollo-federation.md @@ -325,7 +325,7 @@ services.AddGraphQLServer() ## Testing and executing your reference resolvers -After creating an entity, you'll likely wonder "how do I invoke and test this reference resolver?" Entities that define a reference resolver can be queried through the [auto-generated `_entites` query](https://www.apollographql.com/docs/federation/subgraph-spec#understanding-query_entities) at the subgraph level. +After creating an entity, you'll likely wonder "how do I invoke and test this reference resolver?" Entities that define a reference resolver can be queried through the [auto-generated `_entities` query](https://www.apollographql.com/docs/federation/subgraph-spec#understanding-query_entities) at the subgraph level. You'll invoke the query by providing an array of representations using a combination of a `__typename` and key field values to invoke the appropriate resolver. An example query for our `Product` would look something like the following. @@ -435,7 +435,7 @@ services.AddGraphQLServer() -Next, we'll create our `Review` type that has a reference to the `Product` entity. Similar to our first class, we'll need to denote the type's key(s) and the corresponding entity reference resovler(s). +Next, we'll create our `Review` type that has a reference to the `Product` entity. Similar to our first class, we'll need to denote the type's key(s) and the corresponding entity reference resolver(s). diff --git a/website/src/docs/hotchocolate/v13/api-reference/executable.md b/website/src/docs/hotchocolate/v13/api-reference/executable.md index d366ed0fb8e..4a378d98e62 100644 --- a/website/src/docs/hotchocolate/v13/api-reference/executable.md +++ b/website/src/docs/hotchocolate/v13/api-reference/executable.md @@ -13,14 +13,14 @@ public class User public string Name { get; } } -public interface IUserRepostiory +public interface IUserRepository { public IExecutable FindAll(); } public class Query { - public IExecutable GetUsers([Service] IUserRepostiory repo) => + public IExecutable GetUsers([Service] IUserRepository repo) => repo.FindAll(); } ``` diff --git a/website/src/docs/hotchocolate/v13/api-reference/extending-filtering.md b/website/src/docs/hotchocolate/v13/api-reference/extending-filtering.md index 3a7639731c7..9d2be90c102 100644 --- a/website/src/docs/hotchocolate/v13/api-reference/extending-filtering.md +++ b/website/src/docs/hotchocolate/v13/api-reference/extending-filtering.md @@ -253,13 +253,13 @@ You can override `TryHandleOperation` to handle operations. ## The Context -As the visitor and the field handlers are singletons, a context object is passed along with the traversion of input objects. +As the visitor and the field handlers are singletons, a context object is passed along with the traversal of input objects. Field handlers can push data on this context, to make it available for other handlers further down in the tree. The context contains `Types`, `Operations`, `Errors` and `Scopes`. It is very provider-specific what data you need to store in the context. In the case of the `IQueryable` provider, it also contains `RuntimeTypes` and knows if the source is `InMemory` or a database call. -With `Scopes` it is possible to add multiple logical layers to a context. In the case of `IQuerable` this is needed, whenever a new closure starts +With `Scopes` it is possible to add multiple logical layers to a context. In the case of `IQueryable` this is needed, whenever a new closure starts ```csharp // /------------------------ SCOPE 1 -----------------------------\ @@ -302,7 +302,7 @@ A little simplified this is what happens during visitation: # instance[0] = y # level[0] = [] any: { - # Push poperty Address.Street onto the scope + # Push property Address.Street onto the scope # instance[1] = y.Street # level[1] = [] street: { @@ -311,7 +311,7 @@ A little simplified this is what happens during visitation: # level[2] = [y.Street == "221B Baker Street"] eq: "221B Baker Street" } - # Combine everything of the current level and pop the porperty street from the instance + # Combine everything of the current level and pop the property street from the instance # instance[1] = y.Street # level[1] = [y.Street == "221B Baker Street"] } @@ -319,11 +319,11 @@ A little simplified this is what happens during visitation: # instance[2] = x.Company.Addresses # level[2] = [x.Company.Addresses.Any(y => y.Street == "221B Baker Street")] } - # Combine everything of the current level and pop the porperty street from the instance + # Combine everything of the current level and pop the property street from the instance # instance[1] = x.Company # level[1] = [x.Company.Addresses.Any(y => y.Street == "221B Baker Street")] } - # Combine everything of the current level and pop the porperty street from the instance + # Combine everything of the current level and pop the property street from the instance # instance[0] = x # level[0] = [x.Company.Addresses.Any(y => y.Street == "221B Baker Street")] } @@ -340,7 +340,7 @@ The default filtering implementation uses `IQueryable` under the hood. You can c The following example creates a `StringOperationHandler` that supports case insensitive filtering: ```csharp -// The QueryableStringOperationHandler already has an implemenation of CanHandle +// The QueryableStringOperationHandler already has an implementation of CanHandle // It checks if the field is declared in a string operation type and also checks if // the operation of this field uses the `Operation` specified in the override property further // below @@ -366,7 +366,7 @@ public class QueryableStringInvariantEqualsHandler : QueryableStringOperationHan IValueNode value, object parsedValue) { - // We get the instance of the context. This is the expression path to the propert + // We get the instance of the context. This is the expression path to the property // e.g. ~> y.Street Expression property = context.GetInstance(); @@ -374,7 +374,7 @@ public class QueryableStringInvariantEqualsHandler : QueryableStringOperationHan // e.g. ~> eq: "221B Baker Street" if (parsedValue is string str) { - // Creates and returnes the operation + // Creates and returns the operation // e.g. ~> y.Street.ToLower() == "221b baker street" return Expression.Equal( Expression.Call(property, _toLower), diff --git a/website/src/docs/hotchocolate/v13/api-reference/filtering.md b/website/src/docs/hotchocolate/v13/api-reference/filtering.md deleted file mode 100644 index 5564426b56e..00000000000 --- a/website/src/docs/hotchocolate/v13/api-reference/filtering.md +++ /dev/null @@ -1,2031 +0,0 @@ ---- -title: Filtering ---- - -**What are filters?** - -With Hot Chocolate filters, you can expose complex filter objects through your GraphQL API that translates to native database queries. - -The default filter implementation translates filters to expression trees and applies these on `IQueryable`. - -# Overview - -Filters by default work on `IQueryable` but you can also easily customize them to use other interfaces. - -Hot Chocolate by default will inspect your .NET model and infer the possible filter operations from it. - -The following type would yield the following filter operations: - -```csharp -public class Foo -{ - public string Bar { get; set; } -} -``` - -```graphql -input FooFilter { - bar: String - bar_contains: String - bar_ends_with: String - bar_in: [String] - bar_not: String - bar_not_contains: String - bar_not_ends_with: String - bar_not_in: [String] - bar_not_starts_with: String - bar_starts_with: String - AND: [FooFilter!] - OR: [FooFilter!] -} -``` - -**So how can we get started with filters?** - -Getting started with filters is very easy, especially if you do not want to explicitly define filters or customize anything. - -Hot Chocolate will infer the filters directly from your .Net Model and then use a Middleware to apply filters to `IQueryable` or `IEnumerable` on execution. - -> ⚠️ **Note:** If you use more than middleware, keep in mind that **ORDER MATTERS**. - -> ⚠️ **Note:** Be sure to install the `HotChocolate.Types.Filters` NuGet package. - -In the following example, the person resolver returns the `IQueryable` representing the data source. The `IQueryable` represents a not executed database query on which Hot Chocolate can apply filters. - -**Code First** - -The next thing to note is the `UseFiltering` extension method which adds the filter argument to the field and a middleware that can apply those filters to the `IQueryable`. The execution engine will, in the end, execute the `IQueryable` and fetch the data. - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPersons(default)) - .Type>>() - .UseFiltering(); - } -} - -public class Query -{ - public IQueryable GetPersons([Service]IPersonRepository repository) - { - repository.GetPersons(); - } -} -``` - -**Pure Code First** - -The field descriptor attribute `[UseFiltering]` does apply the extension method `UseFiltering()` on the field descriptor. - -```csharp -public class Query -{ - [UseFiltering] - public IQueryable GetPersons([Service]IPersonRepository repository) - { - repository.GetPersons(); - } -} -``` - -**Schema First** - -> ⚠️ **Note:** Schema first does currently not support filtering! - -# Customizing Filters - -A `FilterInputType` defines a GraphQL input type, that Hot Chocolate uses for filtering. You can customize these similar to a normal input type. You can change the name of the type; add, remove, or change operations or directive; and configure the binding behavior. To define and customize a filter we must inherit from `FilterInputType` and configure it like any other type by overriding the `Configure` method. - -```csharp -public class PersonFilterType - : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor - .BindFieldsExplicitly() - .Filter(t => t.Name) - .BindOperationsExplicitly() - .AllowEquals().Name("equals").And() - .AllowContains().Name("contains").And() - .AllowIn().Name("in"); - } -} -``` - -The above filter type defines explicitly which fields allow filtering and what operations these filters allow. Additionally, the filter type changes the name of the equals operation of the filter of the field `Name` to `equals`. - -To make use of the configuration in this filter type, you can provide it to the `UseFiltering` extension method as the generic type argument. - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPerson(default)) - .Type>>(); - .UseFiltering() - } -} -``` - -# Sorting - -Like with filter support you can add sorting support to your database queries. - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPerson(default)) - .Type>>(); - .UseSorting() - } -} -``` - -> Warning: Be sure to install the `HotChocolate.Types.Sorting` NuGet package. - -If you want to combine for instance paging, filtering, and sorting make sure that the order is like follows: - -```csharp -public class QueryType - : ObjectType -{ - protected override void Configure(IObjectTypeDescriptor descriptor) - { - descriptor.Field(t => t.GetPerson(default)) - .UsePaging() - .UseFiltering() - .UseSorting(); - } -} -``` - -**Why is order important?** - -Paging, filtering, and sorting are modular middlewares that form the field resolver pipeline. - -The above example forms the following pipeline: - -`Paging -> Filtering -> Sorting -> Field Resolver` - -The paging middleware will first delegate to the next middleware, which is filtering. - -The filtering middleware will also first delegate to the next middleware, which is sorting. - -The sorting middleware will again first delegate to the next middleware, which is the actual field resolver. - -The field resolver will call `GetPerson` which returns in this example an `IQueryable`. The queryable represents a not yet executed database query. - -After the resolver has been executed and puts its result onto the middleware context the sorting middleware will apply for the sort order on the query. - -After the sorting middleware has been executed and updated the result on the middleware context the filtering middleware will apply its filters on the queryable and updates the result on the middleware context. - -After the paging middleware has been executed and updated the result on the middleware context the paging middleware will slice the data and execute the queryable which will then actually pull in data from the data source. - -So, if we, for instance, applied paging as our last middleware the data set would have been sliced first and then filtered which in most cases is not what we actually want. - -# Filter & Operations Kinds - -You can break down filtering into different kinds of filters that then have different operations. -The filter kind is bound to the type. A string is fundamentally something different than an array or an object. -Each filter kind has different operations that you can apply to it. Some operations are unique to a filter and some operations are shared across multiple filter -e.g. A string filter has string specific operations like `Contains` or `EndsWith` but still shares the operations `Equals` and `NotEquals` with the boolean filter. - -## Filter Kinds - -Hot Chocolate knows following filter kinds - -| Kind | Operations | -| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| String | Equals, In, EndsWith, StartsWith, Contains, NotEquals, NotIn, NotEndsWith, NotStartsWith, NotContains | -| Bool | Equals, NotEquals | -| Object | Equals | -| Array | Some, Any, All, None | -| Comparable | Equals, In, GreaterThan, GreaterThanOrEqual, LowerThan, LowerThanOrEqual, NotEquals, NotIn, NotGreaterThan, NotGreaterThanOrEqual, NotLowerThan, NotLowerThanOrEqual | - -## Operations Kinds - -Hot Chocolate knows following operation kinds - -| Kind | Operations | -| ---------------------- | ----------------------------------------------------------------------------------------------------- | -| Equals | Compares the equality of input value and property value | -| NotEquals | negation of Equals | -| In | Checks if the property value is contained in a given list of input values | -| NotIn | negation of In | -| GreaterThan | checks if the input value is greater than the property value | -| NotGreaterThan | negation of GreaterThan | -| GreaterThanOrEquals | checks if the input value is greater than or equal to the property value | -| NotGreaterThanOrEquals | negation of GreaterThanOrEquals | -| LowerThan | checks if the input value is lower than the property value | -| NotLowerThan | negation of LowerThan | -| LowerThanOrEquals | checks if the input value is lower than or equal to the property value | -| NotLowerThanOrEquals | negation of LowerThanOrEquals | -| EndsWith | checks if the property value ends with the input value | -| NotEndsWith | negation of EndsWith | -| StartsWith | checks if the property value starts with the input value | -| NotStartsWith | negation of StartsWith | -| Contains | checks if the input value is contained in the property value | -| NotContains | negation of Contains | -| Some | checks if at least one element in the collection exists | -| Some | checks if at least one element of the property value meets the condition provided by the input value | -| None | checks if no element of the property value meets the condition provided by the input value | -| All | checks if all least one element of the property value meets the condition provided by the input value | - -## Boolean Filter - -In this example, we look at the filter configuration of a Boolean filter. -As an example, we will use the following model: - -```csharp -public class User -{ - public bool IsOnline {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - isOnline: Boolean -} - -input UserFilter { - isOnline: Boolean - isOnline_not: Boolean - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -### Boolean Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of Boolean filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name) - .AllowEquals().And() - .AllowNotEquals(); - } -} -``` - -## Comparable Filter - -In this example, we look at the filter configuration of a comparable filter. - -A comparable filter is generated for all values that implement IComparable except string and boolean. -e.g. `csharp±enum`, `csharp±int`, `csharp±DateTime`... - -As an example, we will use the following model: - -```csharp -public class User -{ - public int LoggingCount {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - loggingCount: Int -} - -input UserFilter { - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - loggingCount_not: Int - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -### Comparable Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of comparable filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name) - .AllowEquals().And() - .AllowNotEquals().And() - .AllowGreaterThan().And() - .AllowNotGreaterThan().And() - .AllowGreaterThanOrEqals().And() - .AllowNotGreaterThanOrEqals().And() - .AllowLowerThan().And() - .AllowNotLowerThan().And() - .AllowLowerThanOrEqals().And() - .AllowNotLowerThanOrEqals().And() - .AllowIn().And() - .AllowNotIn(); - } -} -``` - -## String Filter - -In this example, we look at the filter configuration of a String filter. -As an example, we will use the following model: - -```csharp -public class User -{ - public string Name {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - name: String -} - -input UserFilter { - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -### String Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of string filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name) - .AllowEquals().And() - .AllowNotEquals().And() - .AllowContains().And() - .AllowNotContains().And() - .AllowStartsWith().And() - .AllowNotStartsWith().And() - .AllowEndsWith().And() - .AllowNotEndsWith().And() - .AllowIn().And() - .AllowNotIn(); - } -} -``` - -## Object Filter - -In this example, we look at the filter configuration of an object filter. - -Hot Chocolate generated object filters for all objects. Since Version 11, Hot Chocolate also generates filter types for nested objects. You can also use object filters to filter over database relations. - -As an example, we will use the following model: - -```csharp -public class User -{ - public Address Address {get;set;} -} - -public class Address -{ - public string Street {get;set;} - - public bool IsPrimary {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - address: Address -} - -type Address { - isPrimary: Boolean - street: String -} - -input UserFilter { - address: AddressFilter - AND: [UserFilter!] - OR: [UserFilter!] -} - -input AddressFilter { - is_primary: Boolean - is_primary_not: Boolean - street: String - street_contains: String - street_ends_with: String - street_in: [String] - street_not: String - street_not_contains: String - street_not_ends_with: String - street_not_in: [String] - street_not_starts_with: String - street_starts_with: String - AND: [AddressFilter!] - OR: [AddressFilter!] -} -``` - -### Object Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of comparable filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Object(x => x.Address); - } -} -``` - -**Configuring a custom nested filter type:** - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Object(x => x.Address).AllowObject(); - } -} - -public class AddressFilterType : FilterInputType
-{ - protected override void Configure( - IFilterInputTypeDescriptor
descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.IsPrimary); - } -} - -// or inline - -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.Object(x => x.Address) - .AllowObject( - y => y.BindFieldsExplicitly().Filter(z => z.IsPrimary)); - } -} - - -``` - -## List Filter - -In this example, we look at the filter configuration of a list filter. - -Hot Chocolate can also generate filters for `IEnumerable`s. Like object filter, Hot Chocolate generates filters for the whole object tree. List filter addresses scalars and object values differently. -In the case the field is a scalar value, Hot Chocolate creates and object type to address the different operations of this scalar. e.g. If you specify filters for a list of strings, Hot Chocolate creates an object type that contains all operations of the string filter. -In case the list holds a complex object, it generates an object filter for this object instead. - -Hot Chocolate implicitly generates filters for all properties that implement `IEnumerable`. -e.g. `csharp±string[]`, `csharp±List`, `csharp±IEnumerable`... - -As an example, we will use the following model: - -```csharp -public class User -{ - public string[] Roles {get;set;} - - public IEnumerable
Addresses {get;set;} -} - -public class Address -{ - public string Street {get;set;} - - public bool IsPrimary {get;set;} -} - -public class Query : ObjectType -{ - [UseFiltering] - public IQueryable GetUsers([Service]UserService users ) - => users.AsQueryable(); -} - -``` - -The produced GraphQL SDL will look like the following: - -```graphql -type Query { - users(where: UserFilter): [User] -} - -type User { - addresses: [Address] - roles: [String] -} - -type Address { - isPrimary: Boolean - street: String -} - -input UserFilter { - addresses_some: AddressFilter - addresses_all: AddressFilter - addresses_none: AddressFilter - addresses_any: Boolean - roles_some: ISingleFilterOfStringFilter - roles_all: ISingleFilterOfStringFilter - roles_none: ISingleFilterOfStringFilter - roles_any: Boolean - AND: [UserFilter!] - OR: [UserFilter!] -} - -input AddressFilter { - is_primary: Boolean - is_primary_not: Boolean - street: String - street_contains: String - street_ends_with: String - street_in: [String] - street_not: String - street_not_contains: String - street_not_ends_with: String - street_not_in: [String] - street_not_starts_with: String - street_starts_with: String - AND: [AddressFilter!] - OR: [AddressFilter!] -} - -input ISingleFilterOfStringFilter { - AND: [ISingleFilterOfStringFilter!] - element: String - element_contains: String - element_ends_with: String - element_in: [String] - element_not: String - element_not_contains: String46 - element_not_ends_with: String - element_not_in: [String] - element_not_starts_with: String - element_starts_with: String - OR: [ISingleFilterOfStringFilter!] -} -``` - -### Array Operation Descriptor - -The example above showed that configuring the operations is optional. -If you want to have access to the actual field input types or allow only a subset of array filters for a given property, you can configure the operation over the `IFilterInputTypeDescriptor` - -```csharp -public class UserFilterType : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.BindFieldsExplicitly(); - descriptor.List(x => x.Addresses) - .AllowSome().And() - .AlloAny().And() - .AllowAll().And() - .AllowNone(); - descriptor.List(x => x.Roles) - .AllowSome().And() - .AlloAny().And() - .AllowAll().And() - .AllowNone(); - } -} -``` - -# Naming Conventions - -\_Hot Chocolate already provides two naming schemes for filters. If you would like to define your own naming scheme or extend existing ones have a look at the documentation of TODO:Link-Filtering - -## Snake Case - -**Configuration** -You can configure the Snake Case with the `UseSnakeCase` extension method convention on the `IFilterConventionDescriptor` - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - descriptor.UseSnakeCase() - } -} - -SchemaBuilder.New().AddConvention(); -// -SchemaBuilder.New().AddConvention(new FilterConvention(x => x.UseSnakeCase()) -``` - -```graphql -input FooBarFilter { - AND: [FooBarFilter!] - nested: String - nested_contains: String - nested_ends_with: String - nested_in: [String] - nested_not: String - nested_not_contains: String - nested_not_ends_with: String -**Change the name of an operation** - nested_not_in: [String] - nested_not_starts_with: String - nested_starts_with: String - OR: [FooBarFilter!] -} - -input FooFilter { - AND: [FooFilter!] - bool: Boolean - bool_not: Boolean - comparable: Short - comparableEnumerable_all: ISingleFilterOfInt16Filter - comparableEnumerable_any: Boolean - comparableEnumerable_none: ISingleFilterOfInt16Filter - comparableEnumerable_some: ISingleFilterOfInt16Filter - comparable_gt: Short - comparable_gte: Short - comparable_in: [Short!] - comparable_lt: Short - comparable_lte: Short - comparable_not: Short - comparable_not_gt: Short - comparable_not_gte: Short - comparable_not_in: [Short!] - comparable_not_lt: Short - comparable_not_lte: Short - object: FooBarFilter - OR: [FooFilter!] -} - -input ISingleFilterOfInt16Filter { - AND: [ISingleFilterOfInt16Filter!] - element: Short - element_gt: Short - element_gte: Short - element_in: [Short!] - element_lt: Short - element_lte: Short - element_not: Short - element_not_gt: Short - element_not_gte: Short - element_not_in: [Short!] - element_not_lt: Short - element_not_lte: Short - OR: [ISingleFilterOfInt16Filter!] -} -``` - -## Pascal Case - -**Configuration** -You can configure the Pascal Case with the `UsePascalCase` extension method convention on the `IFilterConventionDescriptor` - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - descriptor.UsePascalCase() - } -} - -SchemaBuilder.New().AddConvention(); -// -SchemaBuilder.New().AddConvention(new FilterConvention(x => x.UsePascalCase()) -``` - -```graphql -input FooBarFilter { - AND: [FooBarFilter!] - Nested: String - Nested_Contains: String - Nested_EndsWith: String - Nested_In: [String] - Nested_Not: String - Nested_Not_Contains: String - Nested_Not_EndsWith: String - Nested_Not_In: [String] - Nested_Not_StartsWith: String - Nested_StartsWith: String - OR: [FooBarFilter!] -} - -input FooFilter { - AND: [FooFilter!] - Bool: Boolean - Bool_Not: Boolean - Comparable: Short - ComparableEnumerable_All: ISingleFilterOfInt16Filter - ComparableEnumerable_Any: Boolean - ComparableEnumerable_None: ISingleFilterOfInt16Filter - ComparableEnumerable_Some: ISingleFilterOfInt16Filter - Comparable_Gt: Short - Comparable_Gte: Short - Comparable_In: [Short!] - Comparable_Lt: Short - Comparable_Lte: Short - Comparable_Not: Short - Comparable_Not_Gt: Short - Comparable_Not_Gte: Short - Comparable_Not_In: [Short!] - Comparable_Not_Lt: Short - Comparable_Not_Lte: Short - Object: FooBarFilter - OR: [FooFilter!] -} - -input ISingleFilterOfInt16Filter { - AND: [ISingleFilterOfInt16Filter!] - Element: Short - Element_Gt: Short - Element_Gte: Short - Element_In: [Short!] - Element_Lt: Short - Element_Lte: Short - Element_Not_Gt: Short - Element_Not: Short - Element_Not_Gte: Short - Element_Not_In: [Short!] - Element_Not_Lt: Short - Element_Not_Lte: Short - OR: [ISingleFilterOfInt16Filter!] -} -``` - -# Customizing Filter - -Hot Chocolate provides different APIs to customize filtering. You can write custom filter input types, customize the inference behavior of .NET Objects, customize the generated expression, or create a custom visitor, and attach your exotic database. - -**As this can be a bit overwhelming the following questionnaire might help:** - -| | | -| ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| _You do not want all the generated filters and only allow a specific set of filters in a specific case?_ | Custom FilterInputType | -| _You want to change the name of a field or a whole type?_ | Custom FilterInputType | -| _You want to change the name of the `where` argument?_ | Filter Conventions ArgumentName | -| _You want to configure how \_Hot Chocolate_ generates the name and the description of filters in globally? e.g. `PascalCaseFilterType`?\_ | Filter Conventions | -| _You want to configure what the different types of filters are allowed globally?_ | Filter Conventions | -| _Your database provider does not support certain operations of `IQueryable`_ | Filter Conventions | -| _You want to change the naming of a specific lar filter type? e.g._ `foo_contains` _should be_ `foo_like` | Filter Conventions | -| _You want to customize the expression a filter is generating: e.g._ `_equals` _should not be case sensitive?_ | Expression Visitor  | -| _You want to create your own filter types with custom parameters and custom expressions? e.g. GeoJson?_ | Filter Conventions | -| _You have a database client that does not support `IQueryable` and wants to generate filters for it?_ | Custom Visitor | - -# Custom FilterInputType - -Under the hood, filtering is based on top of normal Hot Chocolate input types. You can easily customize them with a very familiar fluent interface. The filter input types follow the same `descriptor` scheme as you are used to from the normal filter input types. Just extend the base class `FilterInputType` and override the descriptor method. - -```csharp -public class User -{ - public string Name {get; set; } - - public string LastName {get; set; } -} - -public class UserFilterType - : FilterInputType -{ - protected override void Configure( IFilterInputTypeDescriptor descriptor) { - - } -} -``` - -`IFilterInputTypeDescriptor` supports most of the methods of `IInputTypeDescriptor` and adds the configuration interface for the filters. By default, Hot Chocolate generates filters for all properties of the type. -If you do want to specify the filters by yourself you can change this behavior with `BindFields`, `BindFieldsExplicitly` or `BindFieldsImplicitly`. - -```csharp -public class UserFilterType - : FilterInputType -{ - protected override void Configure( IFilterInputTypeDescriptor descriptor) { - descriptor.BindFieldsExplicitly(); - descriptor.Filter(x => x.Name); - } -} -``` - -```graphql -input UserFilter { - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -To add or customize a filter you must use `Filter(x => x.Foo)` for scalars `List(x => x.Bar)` for lists and `Object(x => x.Baz)` for nested objects. -These methods will return fluent interfaces to configure the filter for the selected field. - -A field has different filter operations that you can configure. You will find more about filter types and filter operations here TODO:Link -When fields are bound implicitly, meaning filters are added for all properties, you may want to hide a few fields. You can do this with `Ignore(x => Bar)`. -Operations on fields can again be bound implicitly or explicitly. By default, Hot Chocolate generates operations for all fields of the type. -If you do want to specify the operations by yourself you can change this behavior with `BindFilters`, `BindFiltersExplicitly` or `BindFiltersImplicitly`. - -It is also possible to customize the GraphQL field of the operation further. You can change the name, add a description or directive. - -```csharp -public class UserFilterType - : FilterInputType -{ - protected override void Configure( IFilterInputTypeDescriptor descriptor) { - // descriptor.BindFieldsImplicitly(); <- is already the default - descriptor.Filter(x => x.Name) - .BindFilterExplicitly() - .AllowContains() - .Description("Checks if the provided string is contained in the `Name` of a User") - .And() - .AllowEquals() - .Name("exits_with_name") - .Directive("name"); - descriptor.Ignore(x => x.Bar); - } -} -``` - -```graphql -input UserFilter { - exits_with_name: String @name - """ - Checks if the provided string is contained in the `Name` of a User - """ - name_contains: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -**API Documentation** - -| Method | Description | -| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `csharp±BindFields(BindingBehavior bindingBehavior)` | Defines the filter binding behavior. `Explicitly`or `Implicitly`. Default is `Implicitly` | -| `csharp±BindFieldsExplicitly` | Defines that all filters have to be specified explicitly. This means that only the filters are applied that are added with `Filter(x => x.Foo)` | -| `csharp±BindFieldsImplicitly` | The filter type will add filters for all compatible fields. | -| `csharp±Description(string value)` | Adds explanatory text of the `FilterInputType` that can be accessed via introspection. | -| `csharp±Name(NameString value)` | Defines the _GraphQL_ name of the `FilterInputType`. | -| `csharp±Ignore( Expression> property);` | Ignore the specified property. | -| `csharp±Filter( Expression> property)` | Defines a string filter for the selected property. | -| `csharp±Filter( Expression> property)` | Defines a bool filter for the selected property. | -| `csharp±Filter( Expression> property)` | Defines a comparable filter for the selected property. | -| `csharp±Object( Expression> property)` | Defines a object filter for the selected property. | -| `csharp±List( Expression>> property)` | Defines an array string filter for the selected property. | -| `csharp±List( Expression>> property)` | Defines an array bool filter for the selected property. | -| `csharp±List( Expression>> property)` | Defines an array comparable filter for the selected property. | -| `csharp±Filter( Expression>> property)` | Defines an array object filter for the selected property. | -| `csharp±Directive(TDirective directiveInstance)` | Add directive `directiveInstance` to the type | -| `csharp±Directive(TDirective directiveInstance)` | Add directive of type `TDirective` to the type | -| `csharp±Directive(NameString name, params ArgumentNode[] arguments)` | Add directive of type `TDirective` to the type | - -# Filter Conventions - -The customization of filters with `FilterInputTypes` works if you only want to customize one specific filter type. -If you want to change the behavior of all filter types, you want to create a convention for your filters. The filter convention comes with a fluent interface that is close to a type descriptor. -You can see the convention as a configuration object that holds the state that is used by the type system or the execution engine. - -## Get Started - -To use a filter convention, you can extend `FilterConvention` and override the `Configure` method. Alternatively, you can directly configure the convention over the constructor argument. -You then must register your custom convention on the schema builder with `AddConvention`. - -```csharp -public class CustomConvention - : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) { } -} - -SchemaBuilder.New().AddConvention(); -// -SchemaBuilder.New().AddConvention(new FilterConvention(x => /* Config */)); -``` - -## Convention Descriptor Basics - -In this section, we will take a look at the basic features of the filter convention. -The documentation will reference often to `descriptor`. Imagine this `descriptor` as the parameter of the Configure method of the filter convention in the following context: - -```csharp {5} -public class CustomConvention - : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor - ) { } -} - -SchemaBuilder.New().AddConvention(); -``` - -
- -### Argument Name - -With the convention descriptor, you can easily change the argument name of the `FilterInputType`. - -**Configuration** - -```csharp -descriptor.ArgumentName("example_argument_name"); -``` - -**Result** - -```graphql -type Query { - users(example_argument_name: UserFilter): [User] -} -``` - -### Change Name of Scalar List Type Element - -You can change the name of the element of the list type. - -**Configuration** - -```csharp -descriptor.ElementName("example_element_name"); -``` - -**Result** - -```graphql -input ISingleFilterOfInt16Filter { - AND: [ISingleFilterOfInt16Filter!] - example_element_name: Short - example_element_name_gt: Short - example_element_name_gte: Short - example_element_name_in: [Short!] - example_element_name_lt: Short - example_element_name_lte: Short - example_element_name_not: Short - example_element_name_not_gt: Short - example_element_name_not_gte: Short - example_element_name_not_in: [Short!] - example_element_name_not_lt: Short - example_element_name_not_lte: Short - OR: [ISingleFilterOfInt16Filter!] -} -``` - -### Configure Filter Type Name Globally - -You can change the way Hot Chocolate names the types by supplying a delegate. - -This delegate must be of the following type: - -```csharp -public delegate NameString GetFilterTypeName( - IDescriptorContext context, - Type entityType); -``` - -**Configuration** - -```csharp -descriptor.TypeName((context,types) => - context.Naming.GetTypeName(entityType, TypeKind.Object) + "Custom"); -``` - -**Result** - -```graphql -type Query { - users(where: UserCustom): [User] -} -``` - -### Configure Filter Description Globally - -To change the way filter types are named, you have to exchange the factory. - -You have to provide a delegate of the following type: - -```csharp -public delegate string GetFilterTypeDescription( - IDescriptorContext context, - Type entityType); -``` - -**Configuration** - -```csharp -descriptor.TypeName((context,types) => - context.Naming.GetTypeDescription(entityType, TypeKind.Object); + "Custom"); -``` - -**Result** - -```graphql -""" -Custom -""" -input UserFilter { - AND: [UserFilter!] - isOnline: Boolean - isOnline_not: Boolean - OR: [UserFilter!] -} -``` - -### Reset Configuration - -Hot Chocolate shippes with well-defined defaults. To start from scratch, you need to call `Reset()`first. - -**Configuration** - -```csharp -descriptor.Reset(); -``` - -**Result** - -> **⚠ Note:** You will need to add a complete configuration, otherwise the filter will not work as desired! - -## Describe with convention - -With the filter convention descriptor, you have full control over what filters are inferred, their names, operations, and a lot more. -The convention provides a familiar interface to the type configuration. We recommended to first take a look at `Filter & Operations` to understand the concept of filters. This will help you understand how the filter configuration works. - -Filtering has two core components at its heart. First, you have the inference of filters based on .NET types. The second part is an interceptor that translates the filters to the desired output and applies it to the resolver pipeline. These two parts can (and have to) be configured completely independently. With this separation, it is possible to easily extend the behavior. The descriptor is designed to be extendable by extension methods. - -**It's fluent** - -Filter conventions are a completely fluent experience. You can write a whole configuration as a chain of method calls. -This provides a very clean interface, but can, on the other hand, get messy quickly. We recommend using indentation to keep the configuration comprehensible. -You can drill up with `And()`. - -```csharp - descriptor.Operation(FilterOperationKind.Equals).Description("has to be equal"); - descriptor.Operation(FilterOperationKind.NotEquals).Description("has not to be equal"); - descriptor.Type(FilterKind.Comparable).Operation(FilterOperationKind.NotEquals).Description("has to be comparable and not equal") - - - descriptor - .Operation(FilterOperationKind.Equals) - .Description("has to be equal") - .And() - .Operation(FilterOperationKind.NotEquals) - .Description("has not to be equal") - .And() - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.NotEquals) - .Description("has to be comparable and not equal") -``` - -### Configuration of the type system - -In this section, we will focus on the generation of the schema. If you are interested in changing how filters translate to the database, you have to look here TODO:Link - -#### Configure Filter Operations - -There are two ways to configure Operations. - -You can configure a default configuration that applies to all operations of this kind. In this case the configuration for `FilterOperationKind.Equals` would be applied to all `FilterKind` that specify this operation. - -```csharp - descriptor.Operation(FilterOperationKind.Equals) -``` - -If you want to configure a more specific Operation e.g. `FilterOperationKind.Equal` of kind `FilterKind.String`, you can override the default behavior. - -```csharp - descriptor.Type(FilterKind.String).Operation(FilterOperationKind.Equals) -``` - -The operation descriptor allows you to configure the name, the description or even ignore an operation completely - -In this example, we will look at the following input type: - -```graphql -input UserFilter { - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - loggingCount_not: Int - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Change the name of an operation - -To change the name of an operation you need to specify a delegate of the following type: - -```csharp -public delegate NameString CreateFieldName( - FilterFieldDefintion definition, - FilterOperationKind kind); -``` - -**Configuration** - -```csharp {1, 6} - // (A) - // specifies that all not equals operations should be extended with _nada - descriptor - .Operation(FilterOperationKind.NotEquals) - .Name((def, kind) => def.Name + "_nada" ); - // (B) - // specifies that the not equals operations should be extended with _niente. - // this overrides (A) - descriptor - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.NotEquals) - .Name((def, kind) => def.Name + "_niente" ) -``` - -**result** - -```graphql {8,18} -input UserFilter { - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - loggingCount_niente: Int <-- (B) - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - name: String - name_contains: String - name_ends_with: String - name_in: [String] - name_nada: String <-- (A) - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Change the description of an operation - -In the same way, you can configure names you can also configure the description of operations. -You can either set the description for all operations of this kind or only for a specific one in combination with a filter kind. - -**Configuration** - -```csharp - descriptor - .Operation(FilterOperationKind.Equals) - .Description("has to be equal") - .And() - .Operation(FilterOperationKind.NotEquals) - .Description("has not to be equal") - .And() - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.NotEquals) - .Description("has to be comparable and not equal") -``` - -**result** - -```graphql {2-4,11-14, 20-22,27-29} -input UserFilter { - """ - has to be equal - """ - loggingCount: Int - loggingCount_gt: Int - loggingCount_gte: Int - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - """ - has to be comparable and not equal - """ - loggingCount_not: Int - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - """ - has to be equal - """ - name: String - name_contains: String - name_ends_with: String - name_in: [String] - """ - has not to be equal - """ - name_not: String - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Hide Operations - -Hot Chocolate comes pre-configured with a set of operations. If you like to hide operations globally, you can use `Ignore` for it. -If your database provider does not support certain `IQueryable` methods you can just ignore the operation. Ignored operations do not generate filter input types. - -There are multiple ways to ignore an operation: - -**Configuration** - -```csharp - descriptor - .Ignore(FilterOperationKind.Equals) - .Operation(FilterOperationKind.NotEquals) - .Ignore() - .And() - .Type(FilterKind.Comparable) - .Operation(FilterOperationKind.GreaterThanOrEqual) - .Ignore(); -``` - -**result** - -```graphql {2,4, 8,14,18} -input UserFilter { - ↵ - loggingCount_gt: Int - ↵ - loggingCount_in: [Int!] - loggingCount_lt: Int - loggingCount_lte: Int - ↵ - loggingCount_not_gt: Int - loggingCount_not_gte: Int - loggingCount_not_in: [Int!] - loggingCount_not_lt: Int - loggingCount_not_lte: Int - ↵ - name_contains: String - name_ends_with: String - name_in: [String] - ↵ - name_not_contains: String - name_not_ends_with: String - name_not_in: [String] - name_not_starts_with: String - name_starts_with: String - AND: [UserFilter!] - OR: [UserFilter!] -} -``` - -##### Configure Implicit Filter - -The default binding behavior of Hot Chocolate is implicit. Filter types are no exception. -This first may seem like magic, but unfortunately, there is none. It is just code. With `AddImplicitFilter` you can add this pinch of magic to your extension too. -Hot Chocolate creates the filters as it builds the input type. The type iterates over a list of factories sequentially and tries to create a definition for each property. The first factory that can handle the property wins and creates a definition for the filter. - -To configure you have to use the following delegate: - -```csharp - public delegate bool TryCreateImplicitFilter( - IDescriptorContext context, - Type type, - PropertyInfo property, - IFilterConvention filterConventions, - [NotNullWhen(true)] out FilterFieldDefintion? definition); -``` - -| parameter | type | description | -| ------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------- | -| _context_ | `IDescriptorContext` | The context of the type descriptor | -| _type_ | `Type` | The type of the property. `Nullable` is already unwrapped (typeof(T)) | -| _property_ | `PropertyInfo` | The property | -| _filterConventions_ | `IFilterConvention` | The instance of the `IFilterContention`. | -| _definition_ | `out FilterFieldDefintion?` | The generated definition for the property. Return null if the current factory cannot handle the property. | - -If you just want to build your extension for implicit bindings, you can just out a custom `FilterFieldDefinition`. - -It makes sense to encapsulate that logic in a FilterFieldDescriptor though. You can reuse this descriptor also for the fluent configuration interface. - -**Example** - -```csharp -private static bool TryCreateStringFilter( - IDescriptorContext context, - Type type, - PropertyInfo property, - IFilterConvention filterConventions, - [NotNullWhen(true)] out FilterFieldDefintion? definition) -{ - if (type == typeof(string)) - { - var field = new StringFilterFieldDescriptor(context, property, filterConventions); - definition = field.CreateDefinition(); - return true; - } - - definition = null; - return false; -} -``` - -##### Creating a fluent filter extension - -Hot Chocolate provides fluent interfaces for all its APIs. If you want to create an extension that integrates seamlessly with Hot Chocolate it makes sense to also provide fluent interfaces. It makes sense to briefly understand how `Type -> Descriptor -> Definition` work. You can read more about it here //TODO LINK - -Here a quick introduction: - -_Type_ - -A type is a description of a GraphQL Type System Object. Hot Chocolate builds types during schema creation. Types specify how a GraphQL Type looks like. It holds, for example, the definition, fields, interfaces, and all life cycle methods. Type do only exist on startup; they do not exist on runtime. - -_Type Definition_ - -Each type has a definition that describes the type. It holds, for example, the name, description, the CLR type and the field definitions. The field definitions describe the fields that are on the type. - -_Type Descriptor_ - -A type descriptor is a fluent interface to describe the type over the definition. The type descriptor does not have access to the type itself. It operates solely on the definition. - -In the case of filtering, this works nearly the same. The `FilterInputType` is just an extension of the `InputObjectType`. It also has the same _Definition_. The `FilterInputType` stores `FilterOperationField` on this definition. These are extensions of the normal `InputField`'s and extend it by a `FilterOperationKind`. - -With a normal `InputTypeDescriptor` you declare a field by selecting a member. The filter descriptor works a little differently. You declare the `FilterKind` of a member by selecting it and then you declare the operations on this filter. These operations are the input field configuration. - -```csharp -InputTypeDescriptor inputDesc; -inputDesc.Field(x => x.Name) - .Description("This is the name") - - -FilterInputTypeDescriptor inputDesc; -inputDesc.Filter(x => x.Name).AllowEqual().Description("This is the name") -``` - -We have a few case studies that will show you how you can change the inference: - -1. String "\_like" shows an example of how you can easily add a "\_like" operation to the string filter -2. DateTime "from", "to" -3. NetTopologySuite - -> The configuration you see in this case study only shows how you add an operation to an already-existing filter. After this, the job is only half way done. To create a working filter, you must also change the expression visitor. Check the documentation for //TODO: ExpressionVisitor - -##### Case Study: String "\_like" - -**Situation** -The customer has requested a full-text search of the description field of a product. The product owner has promised the feature to the customer two sprints ago and it has still not been shipped. The UX guru of your company has, slightly under pressure, worked out a solution, and together with the frontend team they have already build a prototype. In the heat of the moment, they did not read the user story correctly and, unfortunately, realized last minute that the current filtering API does not fit their needs. The customer does also has to be able to create complex search queries. `This%Test` should match `This is a Test`. As you come back from lunch a hysterical product owner explains the situation to you. To you, it is immediately clear that this can be easily done by using the SQL `like` operator. - -In your codebase you use the `UseFiltering` middleware extensively. In some cases, you also have customized filter types. To cover all possible cases you need - -1. Implicit Binding: `[UseFiltering]` should automagically create the "\_like" filter for every string filter -2. Explicity Binding: `desc.Filter(x => x.Description).AllowLike())` -3. Expression Visitor: You want to directly filter on the database. You use EF Core. - -**Implicit Binding** -With the conventions, it is easy to add operations on already existing filters. We will first look into the configuration for filter inference and in a second step into the code first extension. - -You just need to navigate to the filter you like to modify. `descriptor.Type(FilterKind.String)`. Just add the operation you need with `.Operation(FilterOperationKind.Like)`. The next step is to add factories for the name and the description. - -Altogether this looks like this: - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - descriptor - .Type(FilterKind.String) - .Operation(FilterOperationKind.GreaterThanOrEqual) - .Name((def, kind) => def.Name + "_like" ); - .Description("Full text search. Use % as a placeholder for any symbol"); - } -} -``` - -**Explicit Binding** -By extending the filter descriptor of the string filter you can add a fluent extension that seamlessly integrated with the Hot Chocolate API. - -//TODO: currently there `StringFilterOperationDescriptor` requires `StringFilterFieldDescriptor` instead of `StringFilterFieldDescriptor` and there is no way to `Allow` -//TODO: TYPO ! FilterFieldDefintion -//TODO: Move RewriteType to convention . -//TODO: Move up CreateFieldName - -```csharp -public static class StringLikeFilterExtension -{ - public static IStringFilterOperationDescriptor AllowLike( - IStringFilterFieldDescriptor descriptor) - { - return descriptor.Allow( - FilterOperationKind.ArrayAll, - (ctx, definition) => - { - var operation = new FilterOperation( - typeof(string), FilterOperationKind.ArrayAll, definition.Property); - - return StringFilterOperationDescriptor.New( - ctx, - descriptor, - ctx.GetFilterConvention().CreateFieldName(FilterOperationKind.ArrayAll), - ctx.GetFilterConvention().RewriteType(FilterOperationKind.ArrayAll), - operation); - } - ) - } -} -``` - ---- - -##### Case Study: DateTime "from", "to" - -**Situation** - -1. Implicit Binding: `[UseFiltering]` should automagically create `DateTimeFilter` and the corresponding "\_from" and "\_to". -2. Explicity Binding: `desc.Filter(x => x.OrderedAt).AllowFrom().AllowTo())` -3. Expression Visitor: You want to directly filter on the database. You use EF Core. - -**Configuration** - -It is slightly more complex to create a custom filter than just modifying existing operations. There are a few different parts that must come together to make this work. Implicit and Explicit Bindings are coming together in this example. - -Let's start with the configuration of the convention. By splitting the configuration up into a set of extension methods that can be applied to the convention, it is possible to easily replace sub-components of the extension. e.g. some users might want to use an expression visitor, some others might want to use MognoDB Native. - -- `UseDateTimeFilter` adds support for date-time filters and registers the expression visitor for it. Abstraction for `UseDateTimeFilterImplicitly().UseDateTimeExpression()` - -- `UseDateTimeFilterImplicitly` only registers the configuration of the schema building part of the extension - -- `UseDateTimeExpression` only registers the expression visitor configuration. - -With this separation, a user that prefers to use a custom visitor, can just register the types and skip the expression visitor configuration - -TODO: UseExpressionVisitor should return expression visitor if it already exists -TODO: Reference Definition from Filter Operation instead of property. This way we could reduce complexity further and improve extensibility - -```csharp -public static class DateTimeFilterConventionExtensions -{ - public static IFilterConventionDescriptor UseDateTimeFilter( - this IFilterConventionDescriptor descriptor) => - descriptor.UseDateTimeFilterImplicitly() - .UseDateTimeFilterExpression(); - - public static IFilterConventionDescriptor UseDateTimeFilterImplicitly( - this IFilterConventionDescriptor descriptor) => - descriptor.AddImplicitFilter(TryCreateDateTimeFilter) - .Type(FilterKind.DateTime) - .Operation(FilterOperationKind.GreaterThanOrEquals) - .Name((def, _) => def.Name + "_from") - .Description("") - .And() - .Operation(FilterOperationKind.LowerThanOrEquals) - .Name((def, _) => def.Name + "_to") - .Description("") - .And() - .And(); - - public static IFilterConventionDescriptor UseDateTimeFilterExpression( - this IFilterConventionDescriptor descriptor) => - descriptor.UseExpressionVisitor() - .Kind(FilterKind.DateTime) - .Operation(FilterOperationKind.LowerThanOrEquals) - .Handler(ComparableOperationHandlers.LowerThanOrEquals).And() - .Operation(FilterOperationKind.GreaterThanOrEquals) - .Handler(ComparableOperationHandlers.GreaterThanOrEquals).And() - .And() - .And(); -} -``` - -**Create Date Time Filter Implicitly** - -`DateTime` is a new filter. Hot Chocolate is only aware of its existence because of the delegate passed to `AddImplicitFilter` - -```csharp -private static bool TryCreateDateTimeFilter( - IDescriptorContext context, - Type type, - PropertyInfo property, - IFilterConvention filterConventions, - [NotNullWhen(true)] out FilterFieldDefintion? definition) -{ - if (type == typeof(DateTime)) - { - var field = new DateTimeFilterFieldDescriptor( - context, property, filterConventions); - definition = field.CreateDefinition(); - return true; - } - - definition = null; - return false; -} -``` - -TODO: make filters name based -**Filter Field** - -A filter field is a collection of operations. It holds the configuration of the different operations like _“from”_ and _“to”_. In classic Hot Chocolate fashion there is a descriptor that describes these collections. Hot Chocolate provides the base class `FilterFieldDescriptorBase` you can use as an extension point. There is quite a lot of boilerplate code you need to write. e.g. it makes sense to define an interface for the descriptor. -You find an example here: //TODO LINK - -For the explicit binding, we need to override `CreateOperationDefinition`. In case the filter is bound implicitly, this method is invoked for each operation. -TODO: I think there is an issue with AllowNotEndsWith. - -```csharp -// We override this method for implicity binding -protected override FilterOperationDefintion CreateOperationDefinition( - FilterOperationKind operationKind) => - CreateOperation(operationKind).CreateDefinition(); -``` - -For the implicit binding, we only need to add the methods `AllowFrom` and `AllowTo`. - -```csharp -// The following to methods are for adding the filters explicitly -public IDateTimeFilterOperationDescriptor AllowFrom() => - GetOrCreateOperation(FilterOperationKind.GreaterThanOrEqual); - -public IDateTimeFilterOperationDescriptor AllowTo() => - GetOrCreateOperation(FilterOperationKind.LowerThanOrEqual); - -// This is just a little helper that reduces code duplication -private DateTimeFilterOperationDescriptor GetOrCreateOperation( - FilterOperationKind operationKind) => - Filters.GetOrAddOperation(operationKind, - () => CreateOperation(operationKind)); -``` - -All the methods described above call `CreateOperation`. This method creates the operation descriptor. The `FitlerOperation` that is created here, will also be available for the expression visitor. - -```csharp -// This helper method creates the operation. -private DateTimeFilterOperationDescriptor CreateOperation( - FilterOperationKind operationKind) - { - // This operation is also available in execution. - var operation = new FilterOperation( - typeof(DateTime), - Definition.Kind, - operationKind, - Definition.Property); - - return DateTimeOffsetFilterOperationDescriptor.New( - Context, - this, - CreateFieldName(operationKind), - RewriteType(operationKind), - operation, - FilterConvention); - } -``` - -**Filter Operation** - -In this example; there are two filter operations _"form"_ and _"to"_. The configuration with a descriptor combines explicit and implicit binding. As a base class, you can use `FilterOperationDescriptorBase`. -Here is the interface that is used in this example: - -```csharp -public interface IDateTimeFilterOperationDescriptor - : IDescriptor - , IFluent - { - /// Define filter operations for another field. - IDateTimeFilterFieldDescriptor And(); - - /// Specify the name of the filter operation. - IDateTimeFilterOperationDescriptor Name(NameString value); - - /// Specify the description of the filter operation. - IDateTimeFilterOperationDescriptor Description(string value); - - /// Annotate the operation filter field with a directive. - IDateTimeFilterOperationDescriptor Directive(T directiveInstance) - where T : class; - IDateTimeFilterOperationDescriptor Directive() - where T : class, new(); - IDateTimeFilterOperationDescriptor Directive( - NameString name, - params ArgumentNode[] arguments); - } -``` - -You can find the implementation of this interface here: //TODO link - -**Filter Type Extension** -The last missing piece to complete the integration into Hot Chocolate is an extension of `FilterInputType`. This can again be done as a extension method. - -```csharp -public IStringFilterFieldDescriptor Filter( - Expression> property) -{ - if (property.ExtractMember() is PropertyInfo p) - { - return Fields.GetOrAddDescriptor(p, - () => new StringFilterFieldDescriptor(Context, p)); - } - - throw new ArgumentException( - FilterResources.FilterInputTypeDescriptor_OnlyProperties, - nameof(property)); -} -``` - -//TODO Open this api - ---- - -##### Case Study: Filters for NetTopologySuite - -**Situation** - -> **Note:** If you are searching for `NetTopologySuite`, they are already implemented. Have a look at//TODO LINK - -1. Implicit Binding: `[UseFiltering]` should automagically create `Point` and the corresponding "\_distance" -2. Explicity Binding: `desc.Filter(x => x.Location).AllowDistance()` -3. Expression Visitor: You want to directly filter on the database. You use EF Core. - -Things are different in this case, as there is no longer a 1:1 mapping of input type to method or property. Imagine you want to fetch all bakeries that are near you. In C# you would write something like `dbContext.Bakeries.Where(x => x.Location.Distance(me.Location) < 5)`. This cannot be translated to a _GraphQL_ input type directly. - -A _GraphQL_ query might look like this. - -```graphql -{ - bakeries( - where: { location: { distance: { from: { x: 32, y: 15 }, is_lt: 5 } } } - ) { - name - } -} -``` - -_GraphQL_ input fields cannot have arguments. To work around this issue a data structure is needed that combines the filter payload and the operation. The input type for this example has the following structure. - -```csharp -public class FilterDistance -{ - - public FilterDistance( - FilterPointData from) - { - From = from; - } - /// contains the x and y coordinates. - public FilterPointData From { get; } - - public double Is { get; set; } -} -``` - -```graphql -input FilterDistanceInput { - from: FilterPointDataInput! - is: Float - is_gt: Float - is_gte: Float - is_lt: Float - is_lte: Float - is_in: Float - is_not: Float - is_not_gt: Float - is_not_gte: Float - is_not_lt: Float - is_not_lte: Float - is_not_in: Float -} -``` - -//TODO: Add skip / inopfield! - -Hot Chocolate would generate nested filters for the payload property "From" by default. This can be avoided by declaring the field as input payload. - -```csharp -public class DistanceFilterType - : FilterInputType -{ - protected override void Configure( - IFilterInputTypeDescriptor descriptor) - { - descriptor.Input(x => x.From); - descriptor.Filter(x => x.Is); - } -} -``` - -**Convention & Implicit Factory & Type Descriptor** - -The configuration of the convention, the implicit type factory and the descriptors are very similar to the the two examples before. To not bloat the documentation with duplication we just refer to these two examples and to the reference implementation here //TODO LINK - ---- - -## Translating Filters - -Hot Chocolate can translate incoming filters requests directly onto collections or even on to the database. In the default implementation, the output of this translation is a Linq expression that can be applied to `IQueryable` and `IEnumerable`. You can choose to change the expression that is generated or can even create custom output. Hot Chocolate is using visitors to translate input objects. - -[Learn more about visitors here](/docs/hotchocolate/v13/api-reference/visitors). - -### Expression Filters - -Filter conventions make it easier to change how an expression should be generated. There are three different extension points you can use to change the behavior of the expression visitor. You do not have to worry about the visiting of the input object itself. - -#### Describe the Expression Visitor - -The expression visitor descriptor is accessible through the filter convention. By calling `UseExpressionVisitor` on the convention descriptor you gain access. The expression visitor has the default set of expressions pre-configured. - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor) - { - descriptor.UseExpressionVisitor() - } -} -``` - -The descriptor provides a fluent interface that is very similar to the one of the convention descriptor itself. You have to specify what _operation_ on which _filter kind_ you want to configure. You can drill with `Kind` and `Operation` and go back up by calling `And()`: - -```csharp -public class CustomConvention : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor) - { - descriptor - .UseExpressionVisitor() - .Kind(FilterKind.String) - .Operation(FilterKind.Equals) - .And() - .And() - .Kind(FilterKind.Comparable) - .Operation(FilterKind.In) - } -} -``` - -**Visitation Flow** - -The expression visitor starts as any other visitor at the node you pass in. Usually, this is the node object value node of the filter argument. It then starts the visitation. Every time the visitor _enters_ or _leaves_ an object field, it looks for a matching configuration. If there is no special _enter_ behavior of a field, the visitor generates the expression for the combination of _kind_ and _operation_. - -The next two paragraphs show how the algorithm works in detail. - -_Enter_ - -On _entering_ a field, the visitor tries to get a `FilterFieldEnter` delegate for the `FilterKind` of the current field. If a delegate was found, executed, and the execution return true, the `Enter` method returns the _action_ specified by the delegate. In all other cases, the visitor tries to execute an `OperationHandler` for the combination `FilterKind` and `OperationKind`. If the handler returns true, the expression returned by the handler is added to the context. - -1. Let _field_ be the field that is visited -1. Let _kind_ be the `FilterKind` of _field_ -1. Let _operation_ be the `FilterOperationKind` of _field_ -1. Let _convention_ be the `FilterConvention` used by this visitor -1. Let _enterField_ be the `FilterFieldEnter` delegate for _kind_ on _convention_ -1. If _enterField_ is not null: - 1. Let _action_ be the visitor action of _enterField_ - 1. If _enterField_ returns true: - 1. **return** _action_ -1. Let _operationHander_ be the `FilterOperationHandler` delegate for (_kind_, _operation_) on _convention_ -1. If _operationHandler_ is not null: - 1. Let _expression_ be the expression generated by _operationHandler_ - 1. If _enterField_ returns true: - 1. enqueue _expression_ -1. **return** `SkipAndLeave` - -_Leave_ - -On _entering_ a field, the visitor tries to get and execute a `FilterFieldLeave` delegate for the `FilterKind` of the current field. - -1. Let _field_ be the field that is visited -1. Let _kind_ be the `FilterKind` of _field_ -1. Let _operation_ be the `FilterOperationKind` of _field_ -1. Let _convention_ be the `FilterConvention` used by this visitor -1. Let _leaveField_ be the `FilterFieldLeave` delegate for _kind_ on _convention_ -1. If _leaveField_ is not null: - 1. Execute _leaveField_ - -**Operations** - -The operation descriptor provides you with the method `Handler`. With this method, you can configure, how the expression for the _operation_ of this filter _kind_ is generated. You have to pass a delegate of the following type: - -```csharp -public delegate bool FilterOperationHandler( - FilterOperation operation, - IInputType type, - IValueNode value, - IQueryableFilterVisitorContext context, - [NotNullWhen(true)]out Expression? result); -``` - -This delegate might seem intimidating first, but it is not bad as it looks. If this delegate `true` the `out Expression?` is enqueued on the filters. This means that the visitor will pick it up as it composes the filters. - -| Parameter | Description | -| ---------------------------------------- | --------------------------------------- | -| `FilterOperation operation` | The operation of the current field | -| `IInputType type` | The input type of the current field | -| `IValueNode value` | The AST value node of the current field | -| `IQueryableFilterVisitorContext context` | The context that builds up the state | -| `out Expression? result` | The generated expression | - -Operations handlers can be configured like the following: - -```csharp {10,13} -public class CustomConvention : FilterConvention -{ - protected override void Configure( - IFilterConventionDescriptor descriptor) - { - descriptor - .UseExpressionVisitor() - .Kind(FilterKind.String) - .Operation(FilterKind.Equals) - .Handler(YourVeryOwnHandler.HandleEquals) - .And() - .Operation(FilterKind.NotEquals) - .Handler(YourVeryOwnHandler.HandleNotEquals) - } -} -``` - -TODO: add example - -**Kind** - -There are two extension points on each _filter kind_. You can alter the _entering_ of a filter and the _leaving_. - -**Enter** -You can configure the entering with the following delegate: - -```csharp -public delegate bool FilterFieldEnter( - FilterOperationField field, - ObjectFieldNode node, - IQueryableFilterVisitorContext context, - [NotNullWhen(true)]out ISyntaxVisitorAction? action); -``` - -If this field returns _true_ the filter visitor will continue visitation with the specified _action_ in the out parameter `action`. [Check out the visitor documentation for all possible actions](http://addlinkshere). -If the field does not return true and a visitor action, the visitor will continue and search for a _operation handler_. After this, the visitor will continue with `SkipAndLeave`. - -| Parameter | Description | -| ---------------------------------------- | ------------------------------------ | -| `FilterOperationField field` | The current field | -| `ObjectFieldNode node` | The object node of the current field | -| `IQueryableFilterVisitorContext context` | The context that builds up the state | -| `out ISyntaxVisitorAction? action` | The visitor action | - -**Leave** -You can configure the entering with the following delegate: - -```csharp -public delegate void FilterFieldLeave( - FilterOperationField field, - ObjectFieldNode node, - IQueryableFilterVisitorContext context); -``` - -| Parameter | Description | -| ---------------------------------------- | ------------------------------------ | -| `FilterOperationField field` | The current field | -| `ObjectFieldNode node` | The object node of the current field | -| `IQueryableFilterVisitorContext context` | The context that builds up the state | diff --git a/website/src/docs/hotchocolate/v13/api-reference/language.md b/website/src/docs/hotchocolate/v13/api-reference/language.md index c10b44dcf0a..42359107510 100644 --- a/website/src/docs/hotchocolate/v13/api-reference/language.md +++ b/website/src/docs/hotchocolate/v13/api-reference/language.md @@ -53,7 +53,7 @@ This interface defines the `NodeKind` of the node. | Variable | [A variable](https://spec.graphql.org/June2018/#sec-Language.Variables) | Query (out) | \$foo | | SelectionSet | [specifies a selection of _Field_, _FragmentSpread_ or _InlineFragment_](https://spec.graphql.org/June2018/#sec-Selection-Sets) | Query (out) | {foo bar} | | Field | [Describes a field as a part of a selection set](https://spec.graphql.org/June2018/#sec-Language.Fields) | Query (out) | foo | -| FragmentSpread | [Denotes a spread of a `FragemntDefinition`](https://spec.graphql.org/June2018/#FragmentSpread) | Query (out) | ...f1 | +| FragmentSpread | [Denotes a spread of a `FragmentDefinition`](https://spec.graphql.org/June2018/#FragmentSpread) | Query (out) | ...f1 | | InlineFragment | [Denotes an inline fragment](https://spec.graphql.org/June2018/#sec-Inline-Fragments) | Query (out) | ... on Foo { bar} | | FragmentDefinition | [Defines the definition of a fragment](https://spec.graphql.org/June2018/#FragmentDefinition) | Query (out) | fragment f1 on Foo {} | | IntValue | [Denotes a `int` value](https://spec.graphql.org/June2018/#sec-Int-Value) | Query (in) | 1 | diff --git a/website/src/docs/hotchocolate/v13/api-reference/visitors.md b/website/src/docs/hotchocolate/v13/api-reference/visitors.md index 3b04f1f7ec9..d9b7292a4b3 100644 --- a/website/src/docs/hotchocolate/v13/api-reference/visitors.md +++ b/website/src/docs/hotchocolate/v13/api-reference/visitors.md @@ -147,3 +147,5 @@ query { } } ``` + + diff --git a/website/src/docs/hotchocolate/v13/defining-a-schema/directives.md b/website/src/docs/hotchocolate/v13/defining-a-schema/directives.md index 9360f05818f..eeca390542f 100644 --- a/website/src/docs/hotchocolate/v13/defining-a-schema/directives.md +++ b/website/src/docs/hotchocolate/v13/defining-a-schema/directives.md @@ -408,3 +408,5 @@ If there were more directives in the query, they would be appended to the direct So, now the order would be like the following: `a, b, c, d, e, f`. Every middleware can execute the original resolver function by calling `ResolveAsync()` on the `IDirectiveContext`. + + diff --git a/website/src/docs/hotchocolate/v13/defining-a-schema/scalars.md b/website/src/docs/hotchocolate/v13/defining-a-schema/scalars.md index b3ec052c880..04ea19b1a1e 100644 --- a/website/src/docs/hotchocolate/v13/defining-a-schema/scalars.md +++ b/website/src/docs/hotchocolate/v13/defining-a-schema/scalars.md @@ -2,7 +2,7 @@ title: "Scalars" --- -Scalar types are the primitives of our schema and can hold a specific type of data. They are leaf types, meaning we cannot use e.g. `{ fieldname }` to further drill down into the type. The main purpose of a scalar is to define how a value is serialized and deserialized. +Scalar types are the primitives of our schema and can hold a specific type of data. They are leaf types, meaning we cannot use e.g. `{ fieldName }` to further drill down into the type. The main purpose of a scalar is to define how a value is serialized and deserialized. Besides basic scalars like `String` and `Int`, we can also create custom scalars like `CreditCardNumber` or `SocialSecurityNumber`. These custom scalars can greatly enhance the expressiveness of our schema and help new developers to get a grasp of our API. diff --git a/website/src/docs/hotchocolate/v13/distributed-schema/index.md b/website/src/docs/hotchocolate/v13/distributed-schema/index.md index 3379f49da59..96cc3f70963 100644 --- a/website/src/docs/hotchocolate/v13/distributed-schema/index.md +++ b/website/src/docs/hotchocolate/v13/distributed-schema/index.md @@ -32,7 +32,7 @@ _Schema of the Address Service_ ```sdl type Query { - addressOfPerson(persondId: ID!): Address + addressOfPerson(personId: ID!): Address } type Address { diff --git a/website/src/docs/hotchocolate/v13/distributed-schema/schema-configuration.md b/website/src/docs/hotchocolate/v13/distributed-schema/schema-configuration.md index 5da6418d0eb..9514ca0c663 100644 --- a/website/src/docs/hotchocolate/v13/distributed-schema/schema-configuration.md +++ b/website/src/docs/hotchocolate/v13/distributed-schema/schema-configuration.md @@ -252,7 +252,7 @@ type InventoryInfo { type Query { inventoryInfo(upc: Int!): InventoryInfo! - shippingEsitmate(price: Int!, weight: Int!): InventoryInfo! + shippingEstimate(price: Int!, weight: Int!): InventoryInfo! } ``` @@ -453,7 +453,7 @@ It would be better if the middleware is only applied to the field that needs it. You can use a schema interceptor to apply the middleware to the fields that use it. ```csharp -public class MessageMiddlwareInterceptor : TypeInterceptor +public class MessageMiddlewareInterceptor : TypeInterceptor { public override bool CanHandle(ITypeSystemObjectContext context) { @@ -524,7 +524,7 @@ services .PublishSchemaDefinition( c => c .SetName("inventory") - // Ignores the root types. This removes `inStock` and `shippingEsitmate` + // Ignores the root types. This removes `inStock` and `shippingEstimate` // from the `Query` type of the Gateway .IgnoreRootTypes() // Adds a type extension. diff --git a/website/src/docs/hotchocolate/v13/distributed-schema/schema-federations.md b/website/src/docs/hotchocolate/v13/distributed-schema/schema-federations.md index 2654b5eb107..edbcd1f84f2 100644 --- a/website/src/docs/hotchocolate/v13/distributed-schema/schema-federations.md +++ b/website/src/docs/hotchocolate/v13/distributed-schema/schema-federations.md @@ -21,7 +21,7 @@ You will need to add a package reference to `HotChocolate.Stitching.Redis` to al A domain service has to _publish the schema definition_. The schema is published on the initialization of the schema. By default, a schema is lazy and only initialized when the first request is sent. -You can also initialize the schema on startup with `IntitializeOnStartup`. +You can also initialize the schema on startup with `InitializeOnStartup`. Every schema requires a unique name. This name is used in several places to reference the schema. By calling `PublishSchemaDefinition` you can configure how the schema should be published. diff --git a/website/src/docs/hotchocolate/v13/execution-engine/field-middleware.md b/website/src/docs/hotchocolate/v13/execution-engine/field-middleware.md index 9b8da7357cd..97af96c586f 100644 --- a/website/src/docs/hotchocolate/v13/execution-engine/field-middleware.md +++ b/website/src/docs/hotchocolate/v13/execution-engine/field-middleware.md @@ -258,7 +258,7 @@ descriptor await next(context); // It only makes sense to access the result after calling - // next(context), i.e. after the field resovler and any later + // next(context), i.e. after the field resolver and any later // middleware has finished executing. object? result = context.Result; diff --git a/website/src/docs/hotchocolate/v13/fetching-data/fetching-from-rest.md b/website/src/docs/hotchocolate/v13/fetching-data/fetching-from-rest.md index 592efea1eef..efe3cdc4f19 100644 --- a/website/src/docs/hotchocolate/v13/fetching-data/fetching-from-rest.md +++ b/website/src/docs/hotchocolate/v13/fetching-data/fetching-from-rest.md @@ -251,3 +251,5 @@ You can now head over to your Banana Cake Pop on your GraphQL Server (/graphql) } } ``` + + diff --git a/website/src/docs/hotchocolate/v13/fetching-data/filtering.md b/website/src/docs/hotchocolate/v13/fetching-data/filtering.md index cd7782e0289..290c591c5b0 100644 --- a/website/src/docs/hotchocolate/v13/fetching-data/filtering.md +++ b/website/src/docs/hotchocolate/v13/fetching-data/filtering.md @@ -297,7 +297,7 @@ input UserFilterInput { ## Comparable Filter -Defines filters for `IComparables` like: `bool`, `byte`, `shot`, `int`, `long`, `float`, `double` `decimal`, `Guid`, `DateTime`, `DateTimeOffset` and `TimeSpan` +Defines filters for `IComparable`s like: `bool`, `byte`, `shot`, `int`, `long`, `float`, `double` `decimal`, `Guid`, `DateTime`, `DateTimeOffset` and `TimeSpan` ```csharp public class User diff --git a/website/src/docs/hotchocolate/v13/fetching-data/pagination.md b/website/src/docs/hotchocolate/v13/fetching-data/pagination.md index 7f40dd916cf..a7025a117f4 100644 --- a/website/src/docs/hotchocolate/v13/fetching-data/pagination.md +++ b/website/src/docs/hotchocolate/v13/fetching-data/pagination.md @@ -51,7 +51,7 @@ Adding pagination capabilities to our fields is a breeze. All we have to do is a public class Query { [UsePaging] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) => repository.GetUsers(); } ``` @@ -69,7 +69,7 @@ public class QueryType : ObjectType .UsePaging() .Resolve(context => { - var repository = context.Service(); + var repository = context.Service(); return repository.GetUsers(); }); @@ -88,7 +88,7 @@ To make our life easier, we do not have to write out the _Connection_ types in o public class Query { [UsePaging] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) => repository.GetUsers(); } @@ -130,7 +130,7 @@ We can also specify a custom name for our _Connection_ like the following. public class Query { [UsePaging(ConnectionName = "CustomUsers")] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) { // Omitted code for brevity } @@ -466,7 +466,7 @@ To add _offset-based_ pagination capabilities to our fields we have to add the ` public class Query { [UseOffsetPaging] - public IEnumerable GetUsers([Service] IUserRespository repository) + public IEnumerable GetUsers([Service] IUserRepository repository) => repository.GetUsers(); } ``` @@ -484,7 +484,7 @@ public class QueryType : ObjectType .UseOffsetPaging() .Resolve(context => { - var repository = context.Service(); + var repository = context.Service(); return repository.GetUsers(); }); diff --git a/website/src/docs/hotchocolate/v13/integrations/mongodb.md b/website/src/docs/hotchocolate/v13/integrations/mongodb.md index 86e551699b4..5dc8b319ca7 100644 --- a/website/src/docs/hotchocolate/v13/integrations/mongodb.md +++ b/website/src/docs/hotchocolate/v13/integrations/mongodb.md @@ -261,3 +261,5 @@ public IExecutable GetPersonById( return collection.Find(x => x.Id == id).AsExecutable(); } ``` + + diff --git a/website/src/docs/hotchocolate/v13/integrations/neo4j.md b/website/src/docs/hotchocolate/v13/integrations/neo4j.md index 41b7829e6e4..c5cf387e373 100644 --- a/website/src/docs/hotchocolate/v13/integrations/neo4j.md +++ b/website/src/docs/hotchocolate/v13/integrations/neo4j.md @@ -188,3 +188,5 @@ query GetPersons { } } ``` + + diff --git a/website/src/docs/hotchocolate/v13/integrations/spatial-data.md b/website/src/docs/hotchocolate/v13/integrations/spatial-data.md index a960fc7c2eb..fa80a6fdb93 100644 --- a/website/src/docs/hotchocolate/v13/integrations/spatial-data.md +++ b/website/src/docs/hotchocolate/v13/integrations/spatial-data.md @@ -161,7 +161,7 @@ interface GeoJSONInterface { } ``` -A `NetTopologySuite.Gemeotry` is mapped to this interface by default. +A `NetTopologySuite.Geometry` is mapped to this interface by default. ## Input Types @@ -602,3 +602,5 @@ Additionally we want to provide a way for users, to specify in what CRS they wan Currently we only support filtering for spatial data. We also want to provide a way for users to sort results. This can e.g. be used to find the nearest result for a given point. + + diff --git a/website/src/docs/hotchocolate/v13/migrating/migrate-from-11-to-12.md b/website/src/docs/hotchocolate/v13/migrating/migrate-from-11-to-12.md index 335c520940a..6f2999f5206 100644 --- a/website/src/docs/hotchocolate/v13/migrating/migrate-from-11-to-12.md +++ b/website/src/docs/hotchocolate/v13/migrating/migrate-from-11-to-12.md @@ -222,7 +222,7 @@ services **v12** ```csharp -sevices +services .AddGraphQL() .AddQueryFieldToMutationPayloads(options => { diff --git a/website/src/docs/hotchocolate/v13/migrating/migrate-from-12-to-13.md b/website/src/docs/hotchocolate/v13/migrating/migrate-from-12-to-13.md index 3ffbab9fe47..d35db133b2b 100644 --- a/website/src/docs/hotchocolate/v13/migrating/migrate-from-12-to-13.md +++ b/website/src/docs/hotchocolate/v13/migrating/migrate-from-12-to-13.md @@ -350,7 +350,7 @@ public class Subscription public class Subscription { [Subscribe] - // What's inbetween the curly braces must match an argument name. + // What's in between the curly braces must match an argument name. [Topic("{author}")] public Book BookPublished(string author, [EventMessage] Book book) => book; diff --git a/website/src/docs/hotchocolate/v13/performance/automatic-persisted-queries.md b/website/src/docs/hotchocolate/v13/performance/automatic-persisted-queries.md index 75587efe9e8..f57c8ff329f 100644 --- a/website/src/docs/hotchocolate/v13/performance/automatic-persisted-queries.md +++ b/website/src/docs/hotchocolate/v13/performance/automatic-persisted-queries.md @@ -293,3 +293,5 @@ curl -g 'http://localhost:5000/graphql/?extensions={"persistedQuery":{"version": ```json { "data": { "__typename": "Query" } } ``` + + diff --git a/website/src/docs/hotchocolate/v13/server/batching.md b/website/src/docs/hotchocolate/v13/server/batching.md index 38b43075a28..457006835fc 100644 --- a/website/src/docs/hotchocolate/v13/server/batching.md +++ b/website/src/docs/hotchocolate/v13/server/batching.md @@ -106,7 +106,7 @@ Before you can start using the `@export` directive, you need to register it with ```csharp builder.Services.AddGraphQLServer() .AddExportDirectiveType() - // Ommited for brevity + // Omitted for brevity ``` Now you can annotate the directive on fields in your GraphQL query. @@ -157,3 +157,5 @@ query NewsFeed { ``` In the above example we would export a list of story objects that would be coerced and converted to fit into an input object. + + diff --git a/website/src/docs/hotchocolate/v13/server/files.md b/website/src/docs/hotchocolate/v13/server/files.md index c1049d48952..ba2cb42bfcb 100644 --- a/website/src/docs/hotchocolate/v13/server/files.md +++ b/website/src/docs/hotchocolate/v13/server/files.md @@ -218,7 +218,7 @@ public class Mutation // If the user is allowed to upload the profile picture // we generate the token - var token = "myuploadtoken"; + var token = "myUploadToken"; var uploadUrl = QueryHelpers.AddQueryString(baseUrl, "token", token); @@ -253,7 +253,7 @@ mutation { { "data": { "uploadProfilePicture": { - "uploadUrl": "https://blob.chillicream.com/upload?token=myuploadtoken" + "uploadUrl": "https://blob.chillicream.com/upload?token=myUploadToken" } } } diff --git a/website/src/docs/hotchocolate/v13/server/http-transport.md b/website/src/docs/hotchocolate/v13/server/http-transport.md index eba757cb6c3..43d3fe4962c 100644 --- a/website/src/docs/hotchocolate/v13/server/http-transport.md +++ b/website/src/docs/hotchocolate/v13/server/http-transport.md @@ -222,3 +222,5 @@ builder.Services.AddHttpResponseFormatter(new HttpResponseFormatterOptions { ``` An `Accept` header with the value `application/json` will opt you out of the [GraphQL over HTTP](https://github.com/graphql/graphql-over-http/blob/a1e6d8ca248c9a19eb59a2eedd988c204909ee3f/spec/GraphQLOverHTTP.md) specification. The response `Content-Type` will now be `application/json` and a status code of 200 will be returned for every request, even if it had validation errors or a valid response could not be produced. + + diff --git a/website/src/docs/hotchocolate/v13/server/interceptors.md b/website/src/docs/hotchocolate/v13/server/interceptors.md index 6f81a884736..7bda785801d 100644 --- a/website/src/docs/hotchocolate/v13/server/interceptors.md +++ b/website/src/docs/hotchocolate/v13/server/interceptors.md @@ -47,7 +47,7 @@ public override ValueTask OnCreateAsync(HttpContext context, } ``` -> Warning: `base.OnCreateAsync` should always be invoked, since the default implementation takes care of adding the dependency injection services as well as some important global state variables, such as the `ClaimsPrinicpal`. Not doing this can lead to unexpected issues. +> Warning: `base.OnCreateAsync` should always be invoked, since the default implementation takes care of adding the dependency injection services as well as some important global state variables, such as the `ClaimsPrincipal`. Not doing this can lead to unexpected issues. Most of the configuration will be done through the `OperationRequestBuilder`, injected as argument to this method. @@ -145,7 +145,7 @@ public override ValueTask OnRequestAsync(ISocketConnection connection, } ``` -> Warning: `base.OnRequestAsync` should always be invoked, since the default implementation takes care of adding the dependency injection services as well as some important global state variables, such as the `ClaimsPrinicpal`. Not doing this can lead to unexpected issues. +> Warning: `base.OnRequestAsync` should always be invoked, since the default implementation takes care of adding the dependency injection services as well as some important global state variables, such as the `ClaimsPrincipal`. Not doing this can lead to unexpected issues. Most of the configuration will be done through the `OperationRequestBuilder`, injected as argument to this method.