├── .editorconfig ├── .gitattributes ├── .github ├── codecov.yml ├── dependabot.yml ├── labeler.yml └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ ├── format.yml │ ├── label.yml │ ├── publish.yml │ ├── test.yml │ └── wipcheck.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Directory.Build.props ├── Directory.Build.targets ├── GraphQL.Server.sln ├── LICENSE.md ├── README.md ├── Tests.props ├── assets └── logo.64x64.png ├── docs └── migration │ ├── migration7.md │ └── migration8.md ├── global.json ├── graphql.snk ├── samples ├── Directory.Build.props ├── Samples.Authorization │ ├── Areas │ │ └── Identity │ │ │ └── Pages │ │ │ └── _ViewStart.cshtml │ ├── Data │ │ ├── ApplicationDbContext.cs │ │ └── Migrations │ │ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs │ │ │ ├── 00000000000000_CreateIdentitySchema.cs │ │ │ └── ApplicationDbContextModelSnapshot.cs │ ├── Pages │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ ├── Shared │ │ │ ├── _Layout.cshtml │ │ │ ├── _Layout.cshtml.css │ │ │ ├── _LoginPartial.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── Program.cs │ ├── Properties │ │ ├── launchSettings.json │ │ ├── serviceDependencies.json │ │ └── serviceDependencies.local.json │ ├── Samples.Authorization.csproj │ ├── Schema │ │ └── Query.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ └── js │ │ └── site.js ├── Samples.AzureFunctions │ ├── GraphQL.cs │ ├── Properties │ │ ├── serviceDependencies.json │ │ └── serviceDependencies.local.json │ ├── Samples.AzureFunctions.csproj │ ├── Startup.cs │ └── host.json ├── Samples.Basic │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ └── Samples.Basic.csproj ├── Samples.Complex │ ├── CustomErrorInfoProvider.cs │ ├── GraphQLHttpMiddlewareWithLogs.cs │ ├── MinimumAgeRequirement.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Samples.Complex.csproj │ ├── Startup.cs │ ├── StartupWithRouting.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── Samples.Controller │ ├── Controllers │ │ └── HomeController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ └── Samples.Controller.csproj ├── Samples.Cors │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ └── Samples.Cors.csproj ├── Samples.EndpointRouting │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ └── Samples.EndpointRouting.csproj ├── Samples.Jwt │ ├── Controllers │ │ └── OAuthController.cs │ ├── JwtHelper.cs │ ├── JwtWebSocketAuthenticationService.cs │ ├── Pages │ │ ├── Index.cshtml │ │ └── Index.cshtml.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Samples.Jwt.csproj │ └── SecurityKeyType.cs ├── Samples.MultipleSchemas │ ├── Cats │ │ ├── Cat.cs │ │ ├── CatsData.cs │ │ ├── CatsSchema.cs │ │ ├── Mutation.cs │ │ ├── Query.cs │ │ └── Subscription.cs │ ├── Dogs │ │ ├── Dog.cs │ │ ├── DogsData.cs │ │ ├── DogsSchema.cs │ │ ├── Mutation.cs │ │ ├── Query.cs │ │ └── Subscription.cs │ ├── Pages │ │ ├── Index.cshtml │ │ └── Index.cshtml.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ └── Samples.MultipleSchemas.csproj ├── Samples.NativeAot │ ├── GraphTypes │ │ └── QueryType.cs │ ├── MySchema.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Samples.NativeAot.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── sample-request.json │ └── sample-response.json ├── Samples.Net48 │ ├── Pages │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ └── _ViewImports.cshtml │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Samples.Net48.csproj │ ├── Startup.cs │ ├── app.config │ ├── appsettings.Development.json │ └── appsettings.json ├── Samples.Pages │ ├── Pages │ │ ├── Index.cshtml │ │ └── Index.cshtml.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ └── Samples.Pages.csproj ├── Samples.Schemas.Chat │ ├── Samples.Schemas.Chat.csproj │ ├── Schema │ │ ├── ChatSchema.cs │ │ ├── Event.cs │ │ ├── EventType.cs │ │ ├── Message.cs │ │ ├── MessageInput.cs │ │ ├── Mutation.cs │ │ ├── Query.cs │ │ └── Subscription.cs │ └── Services │ │ ├── Chat.cs │ │ └── IChat.cs └── Samples.Upload │ ├── Mutation.cs │ ├── Pages │ ├── Index.cshtml │ └── Index.cshtml.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Query.cs │ └── Samples.Upload.csproj ├── src ├── All │ └── All.csproj ├── Transports.AspNetCore │ ├── AuthorizationHelper.cs │ ├── AuthorizationParameters.cs │ ├── AuthorizationValidationRule.cs │ ├── AuthorizationVisitor.cs │ ├── AuthorizationVisitorBase.Validation.cs │ ├── AuthorizationVisitorBase.cs │ ├── Compatibility │ │ ├── DictionaryExtensions.cs │ │ ├── DynamicallyAccessedMemberTypes.cs │ │ ├── DynamicallyAccessedMembersAttribute.cs │ │ ├── HostApplicationLifetime.cs │ │ ├── IAsyncDisposable.cs │ │ ├── IsExternalInit.cs │ │ └── QueueExtensions.cs │ ├── Errors │ │ ├── AccessDeniedError.cs │ │ ├── BatchedRequestsNotSupportedError.cs │ │ ├── CsrfProtectionError.cs │ │ ├── FileCountExceededError.cs │ │ ├── FileSizeExceededError.cs │ │ ├── HttpMethodValidationError.cs │ │ ├── IHasPreferredStatusCode.cs │ │ ├── InvalidContentTypeError.cs │ │ ├── InvalidMapError.cs │ │ ├── JsonInvalidError.cs │ │ └── WebSocketSubProtocolNotSupportedError.cs │ ├── ExecutionResultActionResult.cs │ ├── Extensions │ │ ├── GraphQLBuilderExtensions.cs │ │ ├── GraphQLHttpApplicationBuilderExtensions.cs │ │ ├── GraphQLHttpEndpointRouteBuilderExtensions.cs │ │ ├── GraphQLHttpRequestExtensions.cs │ │ └── TaskExtensions.cs │ ├── FormFileGraphType.cs │ ├── GraphQLExecutionActionResult.cs │ ├── GraphQLHttpMiddleware.cs │ ├── GraphQLHttpMiddlewareOptions.cs │ ├── HttpGetValidationRule.cs │ ├── HttpPostValidationRule.cs │ ├── IAuthorizationOptions.cs │ ├── IUserContextBuilder.cs │ ├── MediaTypeAttribute.cs │ ├── SecurityHelper.cs │ ├── Transports.AspNetCore.csproj │ ├── UserContextBuilder.cs │ └── WebSockets │ │ ├── AsyncMessagePump.cs │ │ ├── BaseSubscriptionServer.Observer.cs │ │ ├── BaseSubscriptionServer.cs │ │ ├── GraphQLWebSocketOptions.cs │ │ ├── GraphQLWs │ │ ├── MessageType.cs │ │ ├── PingPayload.cs │ │ └── SubscriptionServer.cs │ │ ├── IOperationMessageProcessor.cs │ │ ├── IWebSocketAuthenticationService.cs │ │ ├── IWebSocketConnection.cs │ │ ├── KeepAliveMode.cs │ │ ├── ReusableMemoryReaderStream.cs │ │ ├── SubscriptionList.cs │ │ ├── SubscriptionsTransportWs │ │ ├── MessageType.cs │ │ └── SubscriptionServer.cs │ │ ├── WebSocketConnection.cs │ │ └── WebSocketWriterStream.cs ├── Ui.Altair │ ├── AltairActionResult.cs │ ├── AltairMiddleware.cs │ ├── AltairOptions.cs │ ├── Extensions │ │ ├── AltairApplicationBuilderExtensions.cs │ │ └── AltairEndpointRouteBuilderExtensions.cs │ ├── Internal │ │ ├── AltairPageModel.cs │ │ └── altair.cshtml │ └── Ui.Altair.csproj ├── Ui.GraphiQL │ ├── Extensions │ │ ├── GraphiQLApplicationBuilderExtensions.cs │ │ └── GraphiQLEndpointRouteBuilderExtensions.cs │ ├── GraphiQLActionResult.cs │ ├── GraphiQLMiddleware.cs │ ├── GraphiQLOptions.cs │ ├── Internal │ │ ├── GraphiQLPageModel.cs │ │ └── graphiql.cshtml │ ├── RequestCredentials.cs │ └── Ui.GraphiQL.csproj ├── Ui.Playground │ ├── Extensions │ │ ├── PlaygroundApplicationBuilderExtensions.cs │ │ └── PlaygroundEndpointRouteBuilderExtensions.cs │ ├── Internal │ │ ├── PlaygroundPageModel.cs │ │ └── playground.cshtml │ ├── PlaygroundActionResult.cs │ ├── PlaygroundMiddleware.cs │ ├── PlaygroundOptions.cs │ ├── RequestCredentials.cs │ └── Ui.Playground.csproj └── Ui.Voyager │ ├── Extensions │ ├── VoyagerApplicationBuilderExtensions.cs │ └── VoyagerEndpointRouteBuilderExtensions.cs │ ├── Internal │ ├── VoyagerPageModel.cs │ └── voyager.cshtml │ ├── RequestCredentials.cs │ ├── Ui.Voyager.csproj │ ├── VoyagerActionResult.cs │ ├── VoyagerMiddleware.cs │ └── VoyagerOptions.cs └── tests ├── ApiApprovalTests ├── ApiApprovalTests.cs ├── ApiApprovalTests.csproj ├── net50+net60+net80 │ └── GraphQL.Server.Transports.AspNetCore.approved.txt ├── net50+net80+netcoreapp31 │ └── GraphQL.Server.Authorization.AspNetCore.approved.txt ├── net80+netcoreapp31 │ ├── GraphQL.Server.Ui.Altair.approved.txt │ ├── GraphQL.Server.Ui.GraphiQL.approved.txt │ ├── GraphQL.Server.Ui.Playground.approved.txt │ └── GraphQL.Server.Ui.Voyager.approved.txt ├── netcoreapp21+netstandard20 │ ├── GraphQL.Server.Authorization.AspNetCore.approved.txt │ └── GraphQL.Server.Transports.AspNetCore.approved.txt ├── netcoreapp31 │ └── GraphQL.Server.Transports.AspNetCore.approved.txt └── netstandard20 │ ├── GraphQL.Server.Ui.Altair.approved.txt │ ├── GraphQL.Server.Ui.GraphiQL.approved.txt │ ├── GraphQL.Server.Ui.Playground.approved.txt │ └── GraphQL.Server.Ui.Voyager.approved.txt ├── Samples.Authorization.Tests ├── EndToEndTests.cs └── Samples.Authorization.Tests.csproj ├── Samples.AzureFunctions.Tests ├── EndToEndTests.cs └── Samples.AzureFunctions.Tests.csproj ├── Samples.Basic.Tests ├── EndToEndTests.cs └── Samples.Basic.Tests.csproj ├── Samples.Complex.Tests ├── BaseTest.cs ├── DITest.cs ├── RequestType.cs ├── ResponseTests.cs ├── Samples.Complex.Tests.csproj ├── Serializer.cs ├── ShouldBeExtensions.cs └── StringExtensions.cs ├── Samples.Controller.Tests ├── EndToEndTests.cs └── Samples.Controller.Tests.csproj ├── Samples.Cors.Tests ├── EndToEndTests.cs └── Samples.Cors.Tests.csproj ├── Samples.EndpointRouting.Tests ├── EndToEndTests.cs └── Samples.EndpointRouting.Tests.csproj ├── Samples.Jwt.Tests ├── EndToEndTests.cs └── Samples.Jwt.Tests.csproj ├── Samples.MultipleSchemas.Tests ├── EndToEndTests.cs └── Samples.MultipleSchemas.Tests.csproj ├── Samples.Net48.Tests ├── EndToEndTests.cs └── Samples.Net48.Tests.csproj ├── Samples.Pages.Tests ├── EndToEndTests.cs └── Samples.Pages.Tests.csproj ├── Samples.Tests ├── Samples.Tests.csproj ├── ServerTests.cs ├── TestServerExtensions.cs └── WebSocketExtensions.cs ├── Samples.Upload.Tests ├── EndToEndTests.cs └── Samples.Upload.Tests.csproj └── Transports.AspNetCore.Tests ├── AuthorizationHelperTests.cs ├── AuthorizationTests.cs ├── BuilderMethodTests.cs ├── ChatTests.cs ├── ExecutionResultActionResultTests.cs ├── FormFileGraphTypeTests.cs ├── Middleware ├── AuthorizationTests.cs ├── BatchTests.cs ├── Cors │ ├── EndpointTests.cs │ └── MiddlewareTests.cs ├── FileUploadTests.cs ├── GetTests.cs ├── MiscTests.cs ├── PostTests.cs └── WebSocketTests.cs ├── ShouldlyExtensions.cs ├── TestServerExtensions.cs ├── Transports.AspNetCore.Tests.csproj ├── UserContextBuilderTests.cs ├── WebSocketExtensions.cs └── WebSockets ├── AsyncContextTests.cs ├── AsyncMessagePumpTests.cs ├── BaseSubscriptionServerTests.cs ├── NewSubscriptionServerTests.cs ├── OldSubscriptionServerTests.cs ├── ReusableMemoryReaderStreamTests.cs ├── SubscriptionListTests.cs ├── TestBaseSubscriptionServer.cs ├── TestNewSubscriptionServer.cs ├── TestOldSubscriptionServer.cs ├── TestWebSocketConnection.cs ├── WebSocketConnectionTests.cs └── WebSocketWriterStreamTests.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # https://docs.codecov.com/docs/codecov-yaml 2 | comment: 3 | behavior: new 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "nuget" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | ignore: 9 | - dependency-name: "GraphQL" 10 | - dependency-name: "GraphQL.DataLoader" 11 | - dependency-name: "GraphQL.MemoryCache" 12 | - dependency-name: "GraphQL.MicrosoftDI" 13 | - dependency-name: "GraphQL.NewtonsoftJson" 14 | - dependency-name: "GraphQL.SystemTextJson" 15 | - dependency-name: "Microsoft.AspNetCore.*" 16 | update-types: ["version-update:semver-major", "version-update:semver-minor"] 17 | 18 | - package-ecosystem: "github-actions" 19 | directory: "/" 20 | schedule: 21 | interval: "daily" 22 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | test: 2 | - tests/*.Tests/**/* 3 | - tests/ApiApprovalTests/**/*.cs 4 | 5 | CI: 6 | - .github/workflows/**/* 7 | - .github/dependabot.yml 8 | - .github/labeler.yml 9 | - .github/codecov.yml 10 | 11 | code style: 12 | - .editorconfig 13 | 14 | documentation: 15 | - README.md 16 | - docs/**/* 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build artifacts 2 | 3 | # ==== NOTE: do not rename this yml file or the run_number will be reset ==== 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | - develop 10 | 11 | env: 12 | DOTNET_NOLOGO: true 13 | DOTNET_CLI_TELEMETRY_OPTOUT: true 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Setup .NET SDK 21 | uses: actions/setup-dotnet@v4 22 | with: 23 | dotnet-version: | 24 | 8.0.x 25 | source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json 26 | env: 27 | NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 28 | - name: Install dependencies 29 | run: dotnet restore 30 | - name: Build solution [Release] 31 | run: dotnet build --no-restore -c Release -p:VersionSuffix=$GITHUB_RUN_NUMBER 32 | - name: Pack solution [Release] 33 | run: dotnet pack --no-restore --no-build -c Release -p:VersionSuffix=$GITHUB_RUN_NUMBER -o out 34 | - name: Upload artifacts 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: Nuget packages 38 | path: | 39 | out/* 40 | - name: Publish Nuget packages to GitHub registry 41 | run: dotnet nuget push "out/*" -k ${{secrets.GITHUB_TOKEN}} 42 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/github/codeql 2 | # https://github.com/github/codeql-action 3 | name: CodeQL analysis 4 | 5 | on: 6 | push: 7 | branches: [master, develop] 8 | pull_request: 9 | branches: [master, develop] 10 | 11 | jobs: 12 | analyze: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout source 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup .NET SDK 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: 8.0.x 23 | source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json 24 | env: 25 | NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 26 | 27 | - name: Initialize CodeQL 28 | uses: github/codeql-action/init@v3 29 | with: 30 | queries: security-and-quality 31 | languages: csharp 32 | 33 | - name: Install dependencies 34 | run: dotnet restore 35 | 36 | - name: Build solution 37 | run: dotnet build --no-restore 38 | 39 | - name: Perform CodeQL Analysis 40 | uses: github/codeql-action/analyze@v3 41 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: Check formatting 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - develop 8 | paths: 9 | - src/** 10 | - tests/** 11 | - samples/** 12 | - .github/workflows/** 13 | - "*.props" 14 | - "*.targets" 15 | - "*.sln" 16 | 17 | env: 18 | DOTNET_NOLOGO: true 19 | DOTNET_CLI_TELEMETRY_OPTOUT: true 20 | 21 | jobs: 22 | format: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout source 26 | uses: actions/checkout@v4 27 | - name: Setup .NET SDK 28 | uses: actions/setup-dotnet@v4 29 | with: 30 | dotnet-version: 8.0.x 31 | source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json 32 | env: 33 | NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 34 | - name: Install dependencies 35 | run: dotnet restore 36 | - name: Check formatting 37 | run: dotnet format --no-restore --verify-no-changes --severity warn || (echo "Run 'dotnet format' to fix issues" && exit 1) 38 | -------------------------------------------------------------------------------- /.github/workflows/label.yml: -------------------------------------------------------------------------------- 1 | # This workflow will triage pull requests and apply a label based on the 2 | # paths that are modified in the pull request. 3 | # 4 | # To use this workflow, you will need to set up a .github/labeler.yml 5 | # file with configuration. For more information, see: 6 | # https://github.com/actions/labeler/blob/master/README.md 7 | 8 | name: Labeler 9 | on: 10 | pull_request_target: 11 | types: 12 | - opened # when PR is opened 13 | 14 | jobs: 15 | label: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/labeler@v5 19 | with: 20 | sync-labels: "" 21 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 22 | continue-on-error: true 23 | -------------------------------------------------------------------------------- /.github/workflows/wipcheck.yml: -------------------------------------------------------------------------------- 1 | name: Check if PR title contains [WIP] 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened # when PR is opened 7 | - edited # when PR is edited 8 | - synchronize # when code is added 9 | - reopened # when a closed PR is reopened 10 | 11 | jobs: 12 | check-title: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Fail build if pull request title contains [WIP] 17 | env: 18 | TITLE: ${{ github.event.pull_request.title }} 19 | if: ${{ contains(github.event.pull_request.title, '[WIP]') }} # This function is case insensitive. 20 | run: | 21 | echo Warning! PR title "$TITLE" contains [WIP]. Remove [WIP] from the title when PR is ready. 22 | exit 1 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | 3 | .vs/ 4 | .idea/ 5 | *.user 6 | *.suo 7 | 8 | [Oo]bj/ 9 | [Bb]in/ 10 | 11 | *.received.txt 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/samples/Samples.Server/bin/Debug/netcoreapp2.0/GraphQL.Samples.Server.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}/samples/Samples.Server", 15 | "stopAtEntry": false, 16 | "internalConsoleOptions": "openOnSessionStart", 17 | "launchBrowser": { 18 | "enabled": true, 19 | "args": "${auto-detect-url}", 20 | "windows": { 21 | "command": "cmd.exe", 22 | "args": "/C start ${auto-detect-url}" 23 | }, 24 | "osx": { 25 | "command": "open" 26 | }, 27 | "linux": { 28 | "command": "xdg-open" 29 | } 30 | }, 31 | "env": { 32 | "ASPNETCORE_ENVIRONMENT": "Development" 33 | }, 34 | "sourceFileMap": { 35 | "/Views": "${workspaceFolder}/Views" 36 | } 37 | }, 38 | { 39 | "name": ".NET Core Attach", 40 | "type": "coreclr", 41 | "request": "attach", 42 | "processId": "${command:pickProcess}" 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/samples/Samples.Server/Samples.Server.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2019 Pekka Heikura 4 | Copyright (c) 2019-2022 Shane Krueger, Ivan Maximov, et al. All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /assets/logo.64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-dotnet/server/3556fe3eb4cda214e8f654b0bfa74014024dd790/assets/logo.64x64.png -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.400", 4 | "allowPrerelease": false, 5 | "rollForward": "latestFeature" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /graphql.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-dotnet/server/3556fe3eb4cda214e8f654b0bfa74014024dd790/graphql.snk -------------------------------------------------------------------------------- /samples/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | $(NoWarn);IDE0053;CS1591;CA1707;CA1848 7 | $(WarningsNotAsErrors);CS8618;CA1716 8 | false 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace AuthorizationSample.Data; 5 | 6 | public class ApplicationDbContext : IdentityDbContext 7 | { 8 | public ApplicationDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

27 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace AuthorizationSample.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Home page"; 5 | } 6 | 7 |
8 |

GraphQL authentication sample

9 |
10 |
11 |
12 |
Authentication status
13 |
14 |

Logged in? @(HttpContext.User.Identity?.IsAuthenticated == true ? "Yes" : "No")

15 | @if (HttpContext.User.Identity?.IsAuthenticated == true) 16 | { 17 |

Current identity claims:

18 |
    19 | @{ 20 | foreach (var claim in HttpContext.User.Claims) 21 | { 22 |
  • @claim.Type: @claim.Value
  • 23 | } 24 | } 25 |
26 |

27 |

28 | } 29 |
30 |
31 |

32 |
33 |
Instructions
34 |
35 |
    36 |
  1. Test GraphQL via link above; should see that only anonymous operations are allowed (e.g. 'hello')
  2. 37 |
  3. Register as a new user; execute database migrations if necessary
  4. 38 |
  5. Log in as new user
  6. 39 |
  7. Test GraphQL via link above; should see operations requiring authentication are allowed, while ones requiring the 'User' role are not (e.g. 'helloRegisteredUser')
  8. 40 |
  9. Click the "Add to User role" button below to add the current user to the 'User' role
  10. 41 |
  11. Log out and log back in
  12. 42 |
  13. Test GraphQL via link above; all operations should work correctly (e.g. 'helloUser')
  14. 43 |
44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace AuthorizationSample.Pages; 6 | 7 | public class IndexModel : PageModel 8 | { 9 | private readonly ILogger _logger; 10 | private readonly RoleManager _roleManager; 11 | private readonly UserManager _userManager; 12 | 13 | public IndexModel(ILogger logger, RoleManager roleManager, UserManager userManager) 14 | { 15 | _logger = logger; 16 | _roleManager = roleManager; 17 | _userManager = userManager; 18 | } 19 | 20 | public void OnGet() 21 | { 22 | 23 | } 24 | 25 | public async Task OnPost([FromForm] FormInfo info) 26 | { 27 | var identity = HttpContext.User.Identity; 28 | if (identity?.Name != null) 29 | { 30 | if (!await _roleManager.RoleExistsAsync("User")) 31 | { 32 | await _roleManager.CreateAsync(new IdentityRole("User")); 33 | } 34 | var user = await _userManager.FindByNameAsync(identity.Name); 35 | if (user != null) 36 | { 37 | if (info.Do == "add") 38 | { 39 | await _userManager.AddToRoleAsync(user, "User"); 40 | } 41 | else if (info.Do == "remove") 42 | { 43 | await _userManager.RemoveFromRoleAsync(user, "User"); 44 | } 45 | } 46 | } 47 | return RedirectToAction("Index"); 48 | } 49 | 50 | public class FormInfo 51 | { 52 | public string? Do { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Pages/Shared/_Layout.cshtml.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | a { 11 | color: #0077cc; 12 | } 13 | 14 | .btn-primary { 15 | color: #fff; 16 | background-color: #1b6ec2; 17 | border-color: #1861ac; 18 | } 19 | 20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 21 | color: #fff; 22 | background-color: #1b6ec2; 23 | border-color: #1861ac; 24 | } 25 | 26 | .border-top { 27 | border-top: 1px solid #e5e5e5; 28 | } 29 | .border-bottom { 30 | border-bottom: 1px solid #e5e5e5; 31 | } 32 | 33 | .box-shadow { 34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 35 | } 36 | 37 | button.accept-policy { 38 | font-size: 1rem; 39 | line-height: inherit; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | white-space: nowrap; 47 | line-height: 60px; 48 | } 49 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Pages/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @inject SignInManager SignInManager 3 | @inject UserManager UserManager 4 | 5 | 27 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using AuthorizationSample 3 | @using AuthorizationSample.Data 4 | @namespace AuthorizationSample.Pages 5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:24703", 7 | "sslPort": 44336 8 | } 9 | }, 10 | "profiles": { 11 | "AuthorizationSample": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "applicationUrl": "https://localhost:7110;http://localhost:5110", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "IIS Express": { 21 | "commandName": "IISExpress", 22 | "launchBrowser": true, 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mssql1": { 4 | "type": "mssql", 5 | "connectionId": "ConnectionStrings:DefaultConnection" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /samples/Samples.Authorization/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mssql1": { 4 | "type": "mssql.local", 5 | "connectionId": "ConnectionStrings:DefaultConnection" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /samples/Samples.Authorization/Samples.Authorization.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/Schema/Query.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using GraphQL; 3 | 4 | namespace AuthorizationSample.Schema; 5 | 6 | public class Query 7 | { 8 | [Description("Does not require authentication.")] 9 | public static string Hello => "Hello anybody."; 10 | 11 | [Authorize] 12 | [Description("Requires authentication, but no role membership or specific policy is enforced.")] 13 | public static string HelloRegisteredUser => "Hello, Registered User!"; 14 | 15 | [Authorize(Roles = "User")] 16 | [Description("Requires membership to the 'User' role.")] 17 | public static string HelloUser => "Hello, User!"; 18 | 19 | [Description("This field does not require authorization, but the argument 'name' does.")] 20 | public static string HelloPerson([Authorize] string? name) => name ?? "Unknown"; 21 | 22 | [Description("This field does not require authorization, but the type it return does")] 23 | public static Person GetPerson => new Person { Name = "User" }; 24 | 25 | [Authorize("MyPolicy")] 26 | [Description("This field requires the 'MyPolicy' policy (which requires the User role) to pass authorization.")] 27 | public static string HelloByPolicy => "Policy Passed!"; 28 | } 29 | 30 | [Authorize] 31 | public class Person 32 | { 33 | public string Name { get; set; } = null!; 34 | } 35 | 36 | [Authorize("MyPolicy")] // this policy requires the User role 37 | public class Mutation 38 | { 39 | [Description("No requirement is defined on this field, but the Mutation type requires the 'MyPolicy' policy (which requires the User role) to pass authorization.")] 40 | public static string Hello => "Hello authenticated user."; 41 | 42 | [AllowAnonymous] 43 | [Description("Although the mutation type requires authentication, this field does not, so as long as only this field is selected, authorization will pass.")] 44 | public static string Unprotected => "Hello anybody."; 45 | } 46 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-AuthorizationSample-fc3360b5-2e37-45ad-89df-e7228434a731;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft.AspNetCore": "Warning" 9 | } 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /samples/Samples.Authorization/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | @media (min-width: 768px) { 6 | html { 7 | font-size: 16px; 8 | } 9 | } 10 | 11 | html { 12 | position: relative; 13 | min-height: 100%; 14 | } 15 | 16 | body { 17 | margin-bottom: 60px; 18 | } -------------------------------------------------------------------------------- /samples/Samples.Authorization/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /samples/Samples.AzureFunctions/GraphQL.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Server.Transports.AspNetCore; 2 | using GraphQL.Server.Ui.GraphiQL; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace Samples.AzureFunctions; 10 | 11 | public class GraphQL 12 | { 13 | [FunctionName("GraphQL")] 14 | public static IActionResult RunGraphQL( 15 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest request, 16 | ILogger log) 17 | { 18 | log.LogInformation("C# HTTP trigger function processed a GraphQL request."); 19 | 20 | return new GraphQLExecutionActionResult(); 21 | } 22 | 23 | [FunctionName("GraphiQL")] 24 | public static IActionResult RunGraphiQL( 25 | [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest request, 26 | ILogger log) 27 | { 28 | log.LogInformation("C# HTTP trigger function processed a request for the GraphiQL UI."); 29 | 30 | return new GraphiQLActionResult(opts => opts.GraphQLEndPoint = "/api/graphql"); // /api/graphql route will call RunGraphQL method 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/Samples.AzureFunctions/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "appInsights1": { 4 | "type": "appInsights" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /samples/Samples.AzureFunctions/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "appInsights1": { 4 | "type": "appInsights.sdk" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /samples/Samples.AzureFunctions/Samples.AzureFunctions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | v4 6 | $(NoWarn);IDE0060 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /samples/Samples.AzureFunctions/Startup.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using Microsoft.Azure.Functions.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Chat = GraphQL.Samples.Schemas.Chat; 5 | 6 | [assembly: FunctionsStartup(typeof(Samples.AzureFunctions.Startup))] 7 | namespace Samples.AzureFunctions; 8 | 9 | public class Startup : FunctionsStartup 10 | { 11 | public override void Configure(IFunctionsHostBuilder builder) 12 | { 13 | builder.Services.AddSingleton(); 14 | 15 | builder.Services.AddGraphQL(b => b 16 | .AddAutoSchema(s => s 17 | .WithMutation() 18 | .WithSubscription()) 19 | .AddSystemTextJson()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/Samples.AzureFunctions/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /samples/Samples.Basic/Program.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using Chat = GraphQL.Samples.Schemas.Chat; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | builder.Services.AddSingleton(); 7 | builder.Services.AddGraphQL(b => b 8 | .AddAutoSchema(s => s 9 | .WithMutation() 10 | .WithSubscription()) 11 | .AddSystemTextJson()); 12 | 13 | var app = builder.Build(); 14 | app.UseDeveloperExceptionPage(); 15 | app.UseWebSockets(); 16 | // configure the graphql endpoint at "/graphql" 17 | app.UseGraphQL("/graphql"); 18 | // configure GraphiQL at "/" 19 | app.UseGraphQLGraphiQL( 20 | "/", 21 | new GraphQL.Server.Ui.GraphiQL.GraphiQLOptions 22 | { 23 | GraphQLEndPoint = "/graphql", 24 | SubscriptionsEndPoint = "/graphql", 25 | }); 26 | 27 | await app.RunAsync(); 28 | -------------------------------------------------------------------------------- /samples/Samples.Basic/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51526/", 7 | "sslPort": 44334 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Typical": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/Samples.Basic/Samples.Basic.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/Samples.Complex/CustomErrorInfoProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using GraphQL.Execution; 3 | using GraphQL.Server.Transports.AspNetCore.Errors; 4 | using Microsoft.AspNetCore.Authorization; 5 | 6 | namespace GraphQL.Samples.Complex; 7 | 8 | /// 9 | /// Custom implementing a dedicated error message for the sample 10 | /// used in this MS article: https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies 11 | /// 12 | public class CustomErrorInfoProvider : ErrorInfoProvider 13 | { 14 | public override ErrorInfo GetInfo(ExecutionError executionError) 15 | { 16 | var info = base.GetInfo(executionError); 17 | 18 | if (executionError is AccessDeniedError accessDeniedError) 19 | info.Message = GetAuthorizationErrorMessage(accessDeniedError); 20 | 21 | return info; 22 | } 23 | 24 | private static string GetAuthorizationErrorMessage(AccessDeniedError error) 25 | { 26 | var errorMessage = new StringBuilder(); 27 | errorMessage.Append(error.Message); 28 | 29 | if (error.PolicyAuthorizationResult != null) 30 | { 31 | foreach (var failedRequirement in error.PolicyAuthorizationResult.Failure!.FailedRequirements) 32 | { 33 | switch (failedRequirement) 34 | { 35 | case MinimumAgeRequirement minimumAgeRequirement: 36 | errorMessage.AppendLine(); 37 | errorMessage.Append("The current user must be at least "); 38 | errorMessage.Append(minimumAgeRequirement.MinimumAge); 39 | errorMessage.Append(" years old."); 40 | break; 41 | } 42 | } 43 | } 44 | 45 | return errorMessage.ToString(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/Samples.Complex/GraphQLHttpMiddlewareWithLogs.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Diagnostics; 4 | using GraphQL.Server.Transports.AspNetCore; 5 | using GraphQL.Transport; 6 | using GraphQL.Types; 7 | 8 | namespace GraphQL.Samples.Complex; 9 | 10 | // Example of a custom GraphQL Middleware that sends execution result to Microsoft.Extensions.Logging API 11 | public class GraphQLHttpMiddlewareWithLogs : GraphQLHttpMiddleware 12 | where TSchema : ISchema 13 | { 14 | private readonly ILogger _logger; 15 | 16 | public GraphQLHttpMiddlewareWithLogs( 17 | RequestDelegate next, 18 | IGraphQLTextSerializer serializer, 19 | IDocumentExecuter documentExecuter, 20 | IServiceScopeFactory serviceScopeFactory, 21 | GraphQLHttpMiddlewareOptions options, 22 | IHostApplicationLifetime hostApplicationLifetime, 23 | ILogger> logger) 24 | : base(next, serializer, documentExecuter, serviceScopeFactory, options, hostApplicationLifetime) 25 | { 26 | _logger = logger; 27 | } 28 | 29 | protected override async Task ExecuteRequestAsync(HttpContext context, GraphQLRequest? request, IServiceProvider serviceProvider, IDictionary? userContext) 30 | { 31 | var timer = Stopwatch.StartNew(); 32 | var ret = await base.ExecuteRequestAsync(context, request, serviceProvider, userContext); 33 | if (ret.Errors != null) 34 | { 35 | _logger.LogError("GraphQL execution completed in {Elapsed} with error(s): {Errors}", timer.Elapsed, ret.Errors); 36 | } 37 | else 38 | _logger.LogInformation("GraphQL execution successfully completed in {Elapsed}", timer.Elapsed); 39 | 40 | return ret; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/Samples.Complex/MinimumAgeRequirement.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | 3 | namespace GraphQL.Samples.Complex; 4 | 5 | /// 6 | /// A enforcing a minimum user age. 7 | /// (sample taken from https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) 8 | /// 9 | public class MinimumAgeRequirement : IAuthorizationRequirement 10 | { 11 | public int MinimumAge { get; } 12 | 13 | public MinimumAgeRequirement(int minimumAge) 14 | { 15 | MinimumAge = minimumAge; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/Samples.Complex/Program.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Events; 3 | 4 | namespace GraphQL.Samples.Complex; 5 | 6 | public class Program 7 | { 8 | public static int Main(string[] args) 9 | { 10 | Log.Logger = new LoggerConfiguration() 11 | .MinimumLevel.Debug() 12 | .MinimumLevel.Override("Microsoft", LogEventLevel.Information) 13 | .Enrich.FromLogContext() 14 | .WriteTo.Console(formatProvider: System.Globalization.CultureInfo.InvariantCulture) 15 | .CreateLogger(); 16 | 17 | try 18 | { 19 | Log.Information("Starting host"); 20 | CreateHostBuilder(args).Build().Run(); 21 | return 0; 22 | } 23 | catch (Exception ex) 24 | { 25 | Log.Fatal(ex, "Host terminated unexpectedly"); 26 | return 1; 27 | } 28 | finally 29 | { 30 | Log.CloseAndFlush(); 31 | } 32 | } 33 | 34 | public static IHostBuilder CreateHostBuilder(string[] args) => Host 35 | .CreateDefaultBuilder(args) 36 | .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()) 37 | .UseSerilog(); 38 | } 39 | -------------------------------------------------------------------------------- /samples/Samples.Complex/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Playground": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "ui/playground", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "applicationUrl": "http://localhost:60341" 11 | }, 12 | "GraphiQL": { 13 | "commandName": "Project", 14 | "launchBrowser": true, 15 | "launchUrl": "ui/graphiql", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | }, 19 | "applicationUrl": "http://localhost:60342" 20 | }, 21 | "Altair": { 22 | "commandName": "Project", 23 | "launchBrowser": true, 24 | "launchUrl": "ui/altair", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | }, 28 | "applicationUrl": "http://localhost:60343" 29 | }, 30 | "Voyager": { 31 | "commandName": "Project", 32 | "launchBrowser": true, 33 | "launchUrl": "ui/voyager", 34 | "environmentVariables": { 35 | "ASPNETCORE_ENVIRONMENT": "Development" 36 | }, 37 | "applicationUrl": "http://localhost:60344" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/Samples.Complex/Samples.Complex.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8;net6;netcoreapp3.1 5 | GraphQL.Samples.Server 6 | GraphQL.Samples.Server 7 | false 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /samples/Samples.Complex/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/Samples.Complex/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Debug" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Debug" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/Samples.Controller/Program.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using Chat = GraphQL.Samples.Schemas.Chat; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | builder.Services.AddControllersWithViews(); 7 | builder.Services.AddSingleton(); 8 | builder.Services.AddGraphQL(b => b 9 | .AddAutoSchema(s => s 10 | .WithMutation() 11 | .WithSubscription()) 12 | .AddSystemTextJson()); 13 | 14 | var app = builder.Build(); 15 | app.UseDeveloperExceptionPage(); 16 | app.UseWebSockets(); 17 | app.UseRouting(); 18 | 19 | app.MapControllerRoute( 20 | name: "default", 21 | pattern: "{controller=Home}/{action=Index}/{id?}"); 22 | 23 | await app.RunAsync(); 24 | -------------------------------------------------------------------------------- /samples/Samples.Controller/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51526/", 7 | "sslPort": 44334 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Typical": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/Samples.Controller/Samples.Controller.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/Samples.Cors/Program.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using Chat = GraphQL.Samples.Schemas.Chat; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | builder.Services.AddSingleton(); 7 | builder.Services.AddGraphQL(b => b 8 | .AddAutoSchema(s => s 9 | .WithMutation() 10 | .WithSubscription()) 11 | .AddSystemTextJson()); 12 | builder.Services.AddRouting(); 13 | builder.Services.AddCors(options => 14 | { 15 | options.AddPolicy("MyCorsPolicy", b => 16 | { 17 | b.AllowCredentials(); 18 | b.WithOrigins("https://localhost:5001"); 19 | }); 20 | }); 21 | 22 | var app = builder.Build(); 23 | app.UseDeveloperExceptionPage(); 24 | app.UseWebSockets(); 25 | app.UseRouting(); 26 | app.UseCors(); 27 | app.UseEndpoints(endpoints => 28 | { 29 | // configure the graphql endpoint at "/graphql" 30 | endpoints.MapGraphQL("/graphql") 31 | .RequireCors("MyCorsPolicy"); 32 | // configure GraphiQL at "/" 33 | endpoints.MapGraphQLGraphiQL("/"); 34 | }); 35 | await app.RunAsync(); 36 | -------------------------------------------------------------------------------- /samples/Samples.Cors/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51526/", 7 | "sslPort": 44334 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Typical": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/Samples.Cors/Samples.Cors.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/Samples.EndpointRouting/Program.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using Chat = GraphQL.Samples.Schemas.Chat; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | builder.Services.AddSingleton(); 7 | builder.Services.AddGraphQL(b => b 8 | .AddAutoSchema(s => s 9 | .WithMutation() 10 | .WithSubscription()) 11 | .AddSystemTextJson()); 12 | builder.Services.AddRouting(); 13 | 14 | var app = builder.Build(); 15 | app.UseDeveloperExceptionPage(); 16 | app.UseWebSockets(); 17 | app.UseRouting(); 18 | app.UseEndpoints(endpoints => 19 | { 20 | // configure the graphql endpoint at "/graphql" 21 | endpoints.MapGraphQL("/graphql"); 22 | // configure GraphiQL at "/" 23 | endpoints.MapGraphQLGraphiQL("/"); 24 | }); 25 | await app.RunAsync(); 26 | -------------------------------------------------------------------------------- /samples/Samples.EndpointRouting/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51526/", 7 | "sslPort": 44334 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Typical": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/Samples.EndpointRouting/Samples.EndpointRouting.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/Samples.Jwt/Controllers/OAuthController.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace GraphQL.Server.Samples.Jwt.Controllers; 5 | 6 | public class OAuthController : Controller 7 | { 8 | private readonly JwtHelper _jwtHelper; 9 | 10 | public OAuthController(JwtHelper jwtHelper) 11 | { 12 | _jwtHelper = jwtHelper; 13 | } 14 | 15 | // sample OAuth2-compatible authorization endpoint supporting the 'client credentials' flow 16 | [HttpGet] 17 | [HttpPost] 18 | [Route("token")] 19 | public IActionResult Token(string grant_type, string client_id, string client_secret) 20 | { 21 | // validate the provided client id and client secret 22 | if (grant_type == "client_credentials" && client_id == "sampleClientId" && client_secret == "sampleSecret") 23 | { 24 | // provide a signed JWT token with an 'Administrator' role claim 25 | var token = _jwtHelper.CreateSignedToken(new Claim("role", "Administrator")); 26 | return Json(new 27 | { 28 | access_token = token.Token, 29 | expires_in = token.ExpiresIn.TotalSeconds, 30 | token_type = "Bearer", 31 | }); 32 | } 33 | 34 | return Unauthorized(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/Samples.Jwt/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace GraphQL.Server.Samples.Jwt; 4 | 5 | public class IndexModel : PageModel 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public IndexModel(ILogger logger) 10 | { 11 | _logger = logger; 12 | } 13 | 14 | public void OnGet() 15 | { 16 | 17 | } 18 | 19 | public string GraphQLEndPoint => "/graphql"; 20 | 21 | public string SubscriptionsEndPoint => "/graphql"; 22 | 23 | public IDictionary Headers { get; } = new Dictionary { 24 | { "Accept", "application/json" }, 25 | { "Content-Type", "application/json" }, 26 | }; 27 | 28 | public string GraphiQLElement => "GraphiQLWithExtensions.GraphiQLWithExtensions"; 29 | 30 | public bool HeaderEditorEnabled => true; 31 | } 32 | -------------------------------------------------------------------------------- /samples/Samples.Jwt/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51526/", 7 | "sslPort": 44334 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Typical": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/Samples.Jwt/Samples.Jwt.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/Samples.Jwt/SecurityKeyType.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Samples.Jwt; 2 | 3 | /// 4 | /// Indicates the type of security 5 | /// 6 | public enum SecurityKeyType 7 | { 8 | /// 9 | /// Represents a SHA256 symmetric security key, capable of creating and validating JWT tokens. 10 | /// 11 | SymmetricSecurityKey, 12 | 13 | /// 14 | /// Represents the public key of a ECDsa asymmetric security key, only capable of validating JWT tokens. 15 | /// 16 | PublicKey, 17 | 18 | /// 19 | /// Represents the private key of a ECDsa asymmetric security key, capable of creating and validating JWT tokens. 20 | /// 21 | PrivateKey, 22 | } 23 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Cats/Cat.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Cats; 2 | 3 | public record Cat([Id] int Id, string Name, string Breed); 4 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Cats/CatsData.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Linq; 2 | 3 | namespace MultipleSchema.Cats; 4 | 5 | public class CatsData 6 | { 7 | private readonly List _cats = new() { 8 | new Cat(1, "Fluffy", "Himalayan"), 9 | new Cat(2, "Jasmine", "Persian"), 10 | new Cat(3, "Angie", "Maine Coon"), 11 | }; 12 | private int _id = 3; 13 | 14 | public IEnumerable Cats 15 | { 16 | get 17 | { 18 | IEnumerable cats; 19 | lock (_cats) 20 | cats = _cats.ToList(); 21 | return cats; 22 | } 23 | } 24 | 25 | public Cat? this[int id] 26 | { 27 | get 28 | { 29 | Cat? cat = null; 30 | lock (_cats) 31 | cat = _cats.FirstOrDefault(c => c.Id == id); 32 | return cat; 33 | } 34 | } 35 | 36 | public Cat Add(string name, string breed) 37 | { 38 | var cat = new Cat(Interlocked.Increment(ref _id), name, breed); 39 | lock (_cats) 40 | _cats.Add(cat); 41 | return cat; 42 | } 43 | 44 | public Cat? Remove(int id) 45 | { 46 | Cat? cat = null; 47 | lock (_cats) 48 | { 49 | for (int i = 0; i < _cats.Count; i++) 50 | { 51 | if (_cats[i].Id == id) 52 | { 53 | cat = _cats[i]; 54 | _cats.RemoveAt(i); 55 | break; 56 | } 57 | } 58 | } 59 | return cat; 60 | } 61 | 62 | public IObservable CatObservable() 63 | => Cats.ToObservable(); 64 | } 65 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Cats/CatsSchema.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Cats; 2 | 3 | public class CatsSchema : Schema 4 | { 5 | public CatsSchema(IServiceProvider serviceProvider) : base(serviceProvider) 6 | { 7 | Query = new AutoRegisteringObjectGraphType(); 8 | Mutation = new AutoRegisteringObjectGraphType(); 9 | Subscription = new AutoRegisteringObjectGraphType(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Cats/Mutation.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Cats; 2 | 3 | public class Mutation 4 | { 5 | public static Cat Add([FromServices] CatsData cats, string name, string breed) 6 | => cats.Add(name, breed); 7 | 8 | public static Cat? Remove([FromServices] CatsData cats, [Id] int id) 9 | => cats.Remove(id); 10 | } 11 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Cats/Query.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Cats; 2 | 3 | public class Query 4 | { 5 | public static Cat? Cat([FromServices] CatsData cats, [Id] int id) 6 | => cats[id]; 7 | 8 | public static IEnumerable Cats([FromServices] CatsData cats) 9 | => cats.Cats; 10 | } 11 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Cats/Subscription.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Cats; 2 | 3 | public class Subscription 4 | { 5 | public static IObservable Cats([FromServices] CatsData cats) 6 | => cats.CatObservable(); 7 | } 8 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Dogs/Dog.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Dogs; 2 | 3 | public record Dog([Id] int Id, string Name, string Breed); 4 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Dogs/DogsData.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Linq; 2 | 3 | namespace MultipleSchema.Dogs; 4 | 5 | public class DogsData 6 | { 7 | private readonly List _dogs = new() { 8 | new Dog(1, "Shadow", "Golden Retriever"), 9 | new Dog(2, "Chance", "American Bulldog"), 10 | new Dog(3, "Lassie", "Collie"), 11 | }; 12 | private int _id = 3; 13 | 14 | public IEnumerable Dogs 15 | { 16 | get 17 | { 18 | IEnumerable dogs; 19 | lock (_dogs) 20 | dogs = _dogs.ToList(); 21 | return dogs; 22 | } 23 | } 24 | 25 | public Dog? this[int id] 26 | { 27 | get 28 | { 29 | Dog? dog = null; 30 | lock (_dogs) 31 | dog = _dogs.FirstOrDefault(c => c.Id == id); 32 | return dog; 33 | } 34 | } 35 | 36 | public Dog Add(string name, string breed) 37 | { 38 | var dog = new Dog(Interlocked.Increment(ref _id), name, breed); 39 | lock (_dogs) 40 | _dogs.Add(dog); 41 | return dog; 42 | } 43 | 44 | public Dog? Remove(int id) 45 | { 46 | Dog? dog = null; 47 | lock (_dogs) 48 | { 49 | for (int i = 0; i < _dogs.Count; i++) 50 | { 51 | if (_dogs[i].Id == id) 52 | { 53 | dog = _dogs[i]; 54 | _dogs.RemoveAt(i); 55 | break; 56 | } 57 | } 58 | } 59 | return dog; 60 | } 61 | 62 | public IObservable DogObservable() 63 | => Dogs.ToObservable(); 64 | } 65 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Dogs/DogsSchema.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Dogs; 2 | 3 | public class DogsSchema : Schema 4 | { 5 | public DogsSchema(IServiceProvider serviceProvider) : base(serviceProvider) 6 | { 7 | Query = new AutoRegisteringObjectGraphType(); 8 | Mutation = new AutoRegisteringObjectGraphType(); 9 | Subscription = new AutoRegisteringObjectGraphType(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Dogs/Mutation.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Dogs; 2 | 3 | public class Mutation 4 | { 5 | public static Dog Add([FromServices] DogsData dogs, string name, string breed) 6 | => dogs.Add(name, breed); 7 | 8 | public static Dog? Remove([FromServices] DogsData dogs, [Id] int id) 9 | => dogs.Remove(id); 10 | } 11 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Dogs/Query.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Dogs; 2 | 3 | public class Query 4 | { 5 | public static Dog? Dog([FromServices] DogsData dogs, [Id] int id) 6 | => dogs[id]; 7 | 8 | public static IEnumerable Dogs([FromServices] DogsData dogs) 9 | => dogs.Dogs; 10 | } 11 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Dogs/Subscription.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleSchema.Dogs; 2 | 3 | public class Subscription 4 | { 5 | public static IObservable Dogs([FromServices] DogsData dogs) 6 | => dogs.DogObservable(); 7 | } 8 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | 3 | 4 | 5 |

6 | Cats 7 |

8 |

9 | Dogs 10 |

11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace MultipleSchema.Pages; 4 | 5 | public class IndexModel : PageModel 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public IndexModel(ILogger logger) 10 | { 11 | _logger = logger; 12 | } 13 | 14 | public void OnGet() 15 | { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services.AddRazorPages(); 4 | builder.Services.AddSingleton(); 5 | builder.Services.AddSingleton(); 6 | builder.Services.AddGraphQL(b => b 7 | .AddSchema() 8 | .AddSchema() 9 | .AddAutoClrMappings() 10 | .AddSystemTextJson()); 11 | 12 | var app = builder.Build(); 13 | app.UseDeveloperExceptionPage(); 14 | app.UseWebSockets(); 15 | // configure the graphql endpoint at "/cats/graphql" 16 | app.UseGraphQL("/cats/graphql"); 17 | // configure the graphql endpoint at "/dogs/graphql" 18 | app.UseGraphQL("/dogs/graphql"); 19 | // configure GraphiQL at "/cats/ui/graphiql" with relative link to api 20 | app.UseGraphQLGraphiQL( 21 | "/cats/ui/graphiql", 22 | new GraphQL.Server.Ui.GraphiQL.GraphiQLOptions 23 | { 24 | GraphQLEndPoint = "../graphql", 25 | SubscriptionsEndPoint = "../graphql", 26 | }); 27 | // configure GraphiQL at "/dogs/ui/graphiql" with relative link to api 28 | app.UseGraphQLGraphiQL( 29 | "/dogs/ui/graphiql", 30 | new GraphQL.Server.Ui.GraphiQL.GraphiQLOptions 31 | { 32 | GraphQLEndPoint = "../graphql", 33 | SubscriptionsEndPoint = "../graphql", 34 | }); 35 | app.MapRazorPages(); 36 | await app.RunAsync(); 37 | -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51526/", 7 | "sslPort": 44334 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Typical": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/Samples.MultipleSchemas/Samples.MultipleSchemas.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | CS8618 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/Samples.NativeAot/GraphTypes/QueryType.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Types; 2 | 3 | namespace GraphQL.Server.Samples.NativeAot.GraphTypes; 4 | 5 | public class QueryType : ObjectGraphType 6 | { 7 | public QueryType() 8 | { 9 | Field("hello") 10 | .Resolve(context => "world"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/Samples.NativeAot/MySchema.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Server.Samples.NativeAot.GraphTypes; 2 | using GraphQL.Types; 3 | 4 | namespace GraphQL.Server.Samples.NativeAot; 5 | 6 | public class MySchema : Schema 7 | { 8 | public MySchema(IServiceProvider services, QueryType queryType) 9 | : base(services) 10 | { 11 | Query = queryType; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/Samples.NativeAot/Program.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using GraphQL.Server.Samples.NativeAot; 3 | using GraphQL.Server.Samples.NativeAot.GraphTypes; 4 | 5 | var builder = WebApplication.CreateSlimBuilder(args); 6 | 7 | builder.Services.AddGraphQL(b => b 8 | .AddSchema() 9 | .AddSystemTextJson()); 10 | 11 | builder.Services.AddTransient(); 12 | 13 | var app = builder.Build(); 14 | 15 | app.UseGraphQLGraphiQL("/"); 16 | app.UseGraphQL(); 17 | 18 | app.Run(); 19 | -------------------------------------------------------------------------------- /samples/Samples.NativeAot/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "launchUrl": "", 9 | "applicationUrl": "http://localhost:5003", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/Samples.NativeAot/Samples.NativeAot.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | true 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/Samples.NativeAot/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/Samples.NativeAot/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /samples/Samples.NativeAot/sample-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": "{ hello }" 3 | } 4 | -------------------------------------------------------------------------------- /samples/Samples.NativeAot/sample-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "hello": "world" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /samples/Samples.Net48/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Home page"; 5 | } 6 |

Hello there

7 | -------------------------------------------------------------------------------- /samples/Samples.Net48/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace GraphQL.Server.Samples.Net48.Pages; 4 | 5 | public class IndexModel : PageModel 6 | { 7 | [Route("/")] 8 | public ActionResult OnGet() 9 | { 10 | return Redirect("/ui/graphql"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/Samples.Net48/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using GraphQL.Server.Samples.Net48 2 | @namespace GraphQL.Server.Samples.Net48.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /samples/Samples.Net48/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | 3 | namespace GraphQL.Server.Samples.Net48; 4 | 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | CreateWebHostBuilder(args).Build().Run(); 10 | } 11 | 12 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 13 | WebHost.CreateDefaultBuilder(args) 14 | .UseStartup(); 15 | } 16 | -------------------------------------------------------------------------------- /samples/Samples.Net48/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:11617", 7 | "sslPort": 44316 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Net48Sample": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/Samples.Net48/Samples.Net48.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | net48 8 | 9 | 10 | full 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /samples/Samples.Net48/Startup.cs: -------------------------------------------------------------------------------- 1 | using Chat = GraphQL.Samples.Schemas.Chat; 2 | 3 | namespace GraphQL.Server.Samples.Net48; 4 | 5 | public class Startup 6 | { 7 | public Startup(IConfiguration configuration) 8 | { 9 | Configuration = configuration; 10 | } 11 | 12 | public IConfiguration Configuration { get; } 13 | 14 | // This method gets called by the runtime. Use this method to add services to the container. 15 | public void ConfigureServices(IServiceCollection services) 16 | { 17 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 18 | 19 | services.AddSingleton(); 20 | services.AddGraphQL(b => b 21 | .AddAutoSchema(s => s 22 | .WithMutation() 23 | .WithSubscription()) 24 | .AddNewtonsoftJson()); 25 | services.AddHostApplicationLifetime(); 26 | } 27 | 28 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 29 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 30 | { 31 | if (env.IsDevelopment()) 32 | app.UseDeveloperExceptionPage(); 33 | app.UseWebSockets(); 34 | app.UseMvc(); 35 | 36 | // configure the graphql endpoint at "/graphql" 37 | app.UseGraphQL("/graphql"); 38 | // configure the GraphiQL endpoint at "/ui/graphql" 39 | app.UseGraphQLGraphiQL( 40 | "/ui/graphql", 41 | new Ui.GraphiQL.GraphiQLOptions 42 | { 43 | GraphQLEndPoint = "/graphql", 44 | SubscriptionsEndPoint = "/graphql", 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/Samples.Net48/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /samples/Samples.Net48/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/Samples.Net48/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /samples/Samples.Pages/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace PagesSample.Pages; 4 | 5 | public class IndexModel : PageModel 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public IndexModel(ILogger logger) 10 | { 11 | _logger = logger; 12 | } 13 | 14 | public void OnGet() 15 | { 16 | 17 | } 18 | 19 | public string GraphQLEndPoint => "/graphql"; 20 | 21 | public string SubscriptionsEndPoint => "/graphql"; 22 | 23 | public IDictionary Headers { get; } = new Dictionary { 24 | { "Accept", "application/json" }, 25 | { "Content-Type", "application/json" }, 26 | }; 27 | 28 | public string GraphiQLElement => "GraphiQLWithExtensions.GraphiQLWithExtensions"; 29 | 30 | public bool HeaderEditorEnabled => true; 31 | } 32 | -------------------------------------------------------------------------------- /samples/Samples.Pages/Program.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using Chat = GraphQL.Samples.Schemas.Chat; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | builder.Services.AddRazorPages(); 7 | builder.Services.AddSingleton(); 8 | builder.Services.AddGraphQL(b => b 9 | .AddAutoSchema(s => s 10 | .WithMutation() 11 | .WithSubscription()) 12 | .AddSystemTextJson()); 13 | 14 | var app = builder.Build(); 15 | app.UseDeveloperExceptionPage(); 16 | app.UseWebSockets(); 17 | // configure the graphql endpoint at "/graphql" 18 | app.UseGraphQL("/graphql"); 19 | app.UseRouting(); 20 | app.MapRazorPages(); 21 | 22 | await app.RunAsync(); 23 | -------------------------------------------------------------------------------- /samples/Samples.Pages/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51526/", 7 | "sslPort": 44334 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Typical": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/Samples.Pages/Samples.Pages.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Samples.Schemas.Chat.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | CS8618;CA1716 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Schema/ChatSchema.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Samples.Schemas.Chat; 2 | 3 | public class ChatSchema : Schema 4 | { 5 | public ChatSchema(IServiceProvider serviceProvider) : base(serviceProvider) 6 | { 7 | Query = new AutoRegisteringObjectGraphType(); 8 | Mutation = new AutoRegisteringObjectGraphType(); 9 | Subscription = new AutoRegisteringObjectGraphType(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Schema/Event.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Samples.Schemas.Chat; 2 | 3 | public class Event 4 | { 5 | public EventType Type { get; set; } 6 | public Message? Message { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Schema/EventType.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Samples.Schemas.Chat; 2 | 3 | public enum EventType 4 | { 5 | NewMessage, 6 | DeleteMessage, 7 | ClearMessages, 8 | } 9 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Schema/Message.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Samples.Schemas.Chat; 2 | 3 | public class Message 4 | { 5 | [Id] 6 | public int Id { get; set; } 7 | 8 | [Name("Message")] 9 | public string Value { get; set; } = null!; 10 | 11 | public string From { get; set; } = null!; 12 | 13 | public DateTime Sent { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Schema/MessageInput.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Samples.Schemas.Chat; 2 | 3 | public class MessageInput 4 | { 5 | public string Message { get; set; } = null!; 6 | public string From { get; set; } = null!; 7 | } 8 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Schema/Mutation.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Samples.Schemas.Chat; 2 | 3 | public class Mutation 4 | { 5 | public static Message AddMessage([FromServices] IChat chatService, MessageInput message) 6 | => chatService.PostMessage(message); 7 | 8 | public static Message? DeleteMessage([FromServices] IChat chatService, [Id] int id) 9 | => chatService.DeleteMessage(id); 10 | 11 | public static int ClearMessages([FromServices] IChat chatService) 12 | => chatService.ClearMessages(); 13 | } 14 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Schema/Query.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Samples.Schemas.Chat; 2 | 3 | public class Query 4 | { 5 | public static Message? LastMessage([FromServices] IChat chatService) 6 | => chatService.LastMessage; 7 | 8 | public static IEnumerable AllMessages([FromServices] IChat chatService, string? from = null) 9 | => from == null ? chatService.GetAllMessages() : chatService.GetMessageFromUser(from); 10 | 11 | public static int Count([FromServices] IChat chatService) 12 | => chatService.Count; 13 | } 14 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Schema/Subscription.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Samples.Schemas.Chat; 2 | 3 | public class Subscription 4 | { 5 | public static IObservable NewMessages([FromServices] IChat chatService, string? from = null) 6 | => from == null ? chatService.SubscribeAll() : chatService.SubscribeFromUser(from); 7 | 8 | public static IObservable Events([FromServices] IChat chatService) 9 | => chatService.SubscribeEvents(); 10 | } 11 | -------------------------------------------------------------------------------- /samples/Samples.Schemas.Chat/Services/IChat.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Samples.Schemas.Chat; 2 | 3 | public interface IChat 4 | { 5 | int Count { get; } 6 | Message? LastMessage { get; } 7 | 8 | int ClearMessages(); 9 | Message? DeleteMessage(int id); 10 | IEnumerable GetAllMessages(); 11 | IEnumerable GetMessageFromUser(string from); 12 | Message PostMessage(MessageInput message); 13 | IObservable SubscribeAll(); 14 | IObservable SubscribeEvents(); 15 | IObservable SubscribeFromUser(string from); 16 | } 17 | -------------------------------------------------------------------------------- /samples/Samples.Upload/Mutation.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using GraphQL.Server.Transports.AspNetCore; 3 | using SixLabors.ImageSharp; 4 | using SixLabors.ImageSharp.Processing; 5 | 6 | namespace Samples.Upload; 7 | 8 | public class Mutation 9 | { 10 | public static async Task Rotate([MediaType("image/*")] IFormFile file, CancellationToken cancellationToken) 11 | { 12 | if (file == null || file.Length == 0) 13 | { 14 | throw new ExecutionError("File is null or empty."); 15 | } 16 | 17 | try 18 | { 19 | // Read the file into an Image 20 | using var sourceStream = file.OpenReadStream(); 21 | using var image = await Image.LoadAsync(sourceStream, cancellationToken); 22 | 23 | // Rotate the image 90 degrees 24 | image.Mutate(x => x.Rotate(90)); 25 | 26 | // Convert the image to a byte array 27 | await using var memoryStream = new MemoryStream(); 28 | await image.SaveAsJpegAsync(memoryStream, cancellationToken); 29 | byte[] imageBytes = memoryStream.ToArray(); 30 | 31 | // Convert byte array to a base-64 string 32 | string base64String = Convert.ToBase64String(imageBytes); 33 | 34 | return base64String; 35 | } 36 | catch (Exception ex) 37 | { 38 | // Handle exceptions (e.g., file is not an image, or unsupported image format) 39 | throw new ExecutionError("Error processing image: " + ex.Message, ex); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/Samples.Upload/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model GraphQL.Server.Samples.Upload.Pages.IndexModel 3 | 4 | 5 | 6 | 7 | Image Upload 8 | 9 | 10 |

Rotate JPEG images

11 |
    12 |
  1. Select a JPEG image
  2. 13 |
  3. Click the "Upload Image" button
  4. 14 |
  5. Wait for the image to be rotated
  6. 15 |
16 |

17 |

18 |

19 | 20 |

21 | 22 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /samples/Samples.Upload/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace GraphQL.Server.Samples.Upload.Pages 4 | { 5 | public class IndexModel : PageModel 6 | { 7 | public void OnGet() 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/Samples.Upload/Program.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using Samples.Upload; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | builder.Services.AddRazorPages(); 7 | builder.Services.AddGraphQL(b => b 8 | .AddAutoSchema(c => c.WithMutation()) 9 | .AddFormFileGraphType() 10 | .AddSystemTextJson()); 11 | 12 | var app = builder.Build(); 13 | app.UseDeveloperExceptionPage(); 14 | app.UseGraphQL(configureMiddleware: opts => opts.ReadFormOnPost = true); 15 | app.UseRouting(); 16 | app.MapRazorPages(); 17 | 18 | await app.RunAsync(); 19 | -------------------------------------------------------------------------------- /samples/Samples.Upload/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51526/", 7 | "sslPort": 44334 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Typical": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/Samples.Upload/Query.cs: -------------------------------------------------------------------------------- 1 | namespace Samples.Upload; 2 | 3 | public class Query 4 | { 5 | public static string Hello() => "Hello World!"; 6 | } 7 | -------------------------------------------------------------------------------- /samples/Samples.Upload/Samples.Upload.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/All/All.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netcoreapp3.1 5 | GraphQL Server meta package with all the packages you need to get started 6 | GraphQL;transport;server;WebSockets;subscriptions;middleware;authentication;authorization 7 | false 8 | $(NoWarn);NU5128 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/AuthorizationValidationRule.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore; 2 | 3 | /// 4 | /// Validates a document against the configured set of policy and role requirements. 5 | /// 6 | public class AuthorizationValidationRule : ValidationRuleBase 7 | { 8 | /// 9 | public override async ValueTask GetPreNodeVisitorAsync(ValidationContext context) 10 | { 11 | var user = context.User 12 | ?? throw new InvalidOperationException("User could not be retrieved from ValidationContext. Please be sure it is set in ExecutionOptions.User."); 13 | var provider = context.RequestServices 14 | ?? throw new MissingRequestServicesException(); 15 | var authService = provider.GetService() 16 | ?? throw new InvalidOperationException("An instance of IAuthorizationService could not be pulled from the dependency injection framework."); 17 | 18 | var visitor = new AuthorizationVisitor(context, user, authService); 19 | // if the schema fails authentication, report the error and do not perform any additional authorization checks. 20 | return await visitor.ValidateSchemaAsync(context) ? visitor : null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/AuthorizationVisitor.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore; 2 | 3 | /// 4 | public class AuthorizationVisitor : AuthorizationVisitorBase 5 | { 6 | /// 7 | public AuthorizationVisitor(ValidationContext context, ClaimsPrincipal claimsPrincipal, IAuthorizationService authorizationService) 8 | : base(context) 9 | { 10 | if (context == null) 11 | throw new ArgumentNullException(nameof(context)); 12 | ClaimsPrincipal = claimsPrincipal ?? throw new ArgumentNullException(nameof(claimsPrincipal)); 13 | AuthorizationService = authorizationService ?? throw new ArgumentNullException(nameof(authorizationService)); 14 | } 15 | 16 | /// 17 | /// Gets the user that this authorization visitor will authenticate against. 18 | /// 19 | protected ClaimsPrincipal ClaimsPrincipal { get; } 20 | 21 | /// 22 | /// Gets the authorization service that is used to authorize policy requests. 23 | /// 24 | protected IAuthorizationService AuthorizationService { get; } 25 | 26 | /// 27 | protected override bool IsAuthenticated 28 | => ClaimsPrincipal.Identity?.IsAuthenticated ?? false; 29 | 30 | /// 31 | protected override bool IsInRole(string role) 32 | => ClaimsPrincipal.IsInRole(role); 33 | 34 | /// 35 | protected override ValueTask AuthorizeAsync(string policy) 36 | => new(AuthorizationService.AuthorizeAsync(ClaimsPrincipal, policy)); 37 | } 38 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Compatibility/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_0 2 | 3 | namespace GraphQL.Server.Transports.AspNetCore; 4 | 5 | internal static class DictionaryExtensions 6 | { 7 | public static bool TryAdd(this IDictionary dic, TKey key, TValue value) 8 | { 9 | if (!dic.ContainsKey(key)) 10 | { 11 | dic.Add(key, value); 12 | return true; 13 | } 14 | return false; 15 | } 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Compatibility/HostApplicationLifetime.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_0 || NETCOREAPP2_1 2 | 3 | namespace GraphQL.Server.Transports.AspNetCore 4 | { 5 | /// 6 | /// Provides a signal when the application is shutting down. 7 | /// 8 | public interface IHostApplicationLifetime 9 | { 10 | /// 11 | CancellationToken ApplicationStopping { get; } 12 | } 13 | 14 | /// 15 | public class HostApplicationLifetime : IHostApplicationLifetime, IHostedService 16 | { 17 | private readonly CancellationTokenSource _cts = new(); 18 | 19 | /// 20 | public CancellationToken ApplicationStopping => _cts.Token; 21 | 22 | /// 23 | public Task StartAsync(CancellationToken cancellationToken) 24 | => Task.CompletedTask; 25 | 26 | /// 27 | public Task StopAsync(CancellationToken cancellationToken) 28 | { 29 | _cts.Cancel(); 30 | return Task.CompletedTask; 31 | } 32 | } 33 | } 34 | 35 | namespace GraphQL.Server 36 | { 37 | /// 38 | /// Extension methods for . 39 | /// 40 | public static partial class ServiceCollectionExtensions 41 | { 42 | /// 43 | /// Registers within the dependency injection framework. 44 | /// "Native" IHostApplicationLifetime is not available for .NET Core 2.1 / .NET Framework 4.8 45 | /// 46 | public static void AddHostApplicationLifetime(this IServiceCollection services) 47 | { 48 | services.AddSingleton(); 49 | services.AddSingleton(provider => (HostApplicationLifetime)provider.GetRequiredService()); 50 | } 51 | } 52 | } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Compatibility/IAsyncDisposable.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_0 || NETCOREAPP2_1 2 | 3 | namespace GraphQL.Server.Transports.AspNetCore; 4 | 5 | internal interface IAsyncDisposable 6 | { 7 | ValueTask DisposeAsync(); 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Compatibility/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | #if !NET5_0_OR_GREATER 2 | 3 | namespace System.Runtime.CompilerServices; 4 | 5 | /// 6 | /// Reserved to be used by the compiler for tracking metadata. 7 | /// This class should not be used by developers in source code. 8 | /// 9 | internal static class IsExternalInit 10 | { 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Compatibility/QueueExtensions.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD2_0 2 | 3 | namespace GraphQL.Server.Transports.AspNetCore; 4 | 5 | internal static class QueueExtensions 6 | { 7 | public static bool TryPeek(this Queue queue, out T? value) 8 | { 9 | if (queue.Count == 0) 10 | { 11 | value = default; 12 | return false; 13 | } 14 | else 15 | { 16 | value = queue.Peek(); 17 | return true; 18 | } 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/AccessDeniedError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents an error indicating that the user is not allowed access to the specified resource. 5 | /// 6 | public class AccessDeniedError : ValidationError, IHasPreferredStatusCode 7 | { 8 | /// 9 | public AccessDeniedError(string resource) 10 | : base($"Access denied for {resource}.") 11 | { 12 | } 13 | 14 | /// 15 | public AccessDeniedError(string resource, GraphQLParser.ROM originalQuery, params ASTNode[] nodes) 16 | : base(originalQuery, null!, $"Access denied for {resource}.", nodes) 17 | { 18 | } 19 | 20 | /// 21 | /// Returns the policy that would allow access to these node(s). 22 | /// 23 | public string? PolicyRequired { get; set; } 24 | 25 | /// 26 | public AuthorizationResult? PolicyAuthorizationResult { get; set; } 27 | 28 | /// 29 | /// Returns the list of role memberships that would allow access to these node(s). 30 | /// 31 | public List? RolesRequired { get; set; } 32 | 33 | /// 34 | public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.Forbidden; 35 | } 36 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/BatchedRequestsNotSupportedError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents an error indicating that batched requests are not supported. 5 | /// 6 | public class BatchedRequestsNotSupportedError : RequestError 7 | { 8 | /// 9 | public BatchedRequestsNotSupportedError() : base("Batched requests are not supported.") { } 10 | } 11 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/CsrfProtectionError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents an error indicating that the request may not have triggered a CORS preflight request. 5 | /// 6 | public class CsrfProtectionError : RequestError 7 | { 8 | /// 9 | public CsrfProtectionError(IEnumerable headersRequired) : base($"This request requires a non-empty header from the following list: {FormatHeaders(headersRequired)}.") { } 10 | 11 | /// 12 | public CsrfProtectionError(IEnumerable headersRequired, Exception innerException) : base($"This request requires a non-empty header from the following list: {FormatHeaders(headersRequired)}. {innerException.Message}") { } 13 | 14 | private static string FormatHeaders(IEnumerable headersRequired) 15 | => string.Join(", ", headersRequired.Select(x => $"'{x}'")); 16 | } 17 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/FileCountExceededError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents an error when too many files are uploaded in a GraphQL request. 5 | /// 6 | public class FileCountExceededError : RequestError, IHasPreferredStatusCode 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public FileCountExceededError() 12 | : base("File uploads exceeded.") 13 | { 14 | } 15 | 16 | /// 17 | public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.RequestEntityTooLarge; 18 | } 19 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/FileSizeExceededError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents an error when a file exceeds the allowed size limit in a GraphQL upload. 5 | /// 6 | public class FileSizeExceededError : RequestError, IHasPreferredStatusCode 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public FileSizeExceededError() 12 | : base("File size limit exceeded.") 13 | { 14 | } 15 | 16 | /// 17 | public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.RequestEntityTooLarge; 18 | } 19 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/HttpMethodValidationError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents a validation error indicating that the requested operation is not valid 5 | /// for the type of HTTP request. 6 | /// 7 | public class HttpMethodValidationError : ValidationError, IHasPreferredStatusCode 8 | { 9 | /// 10 | public HttpMethodValidationError(GraphQLParser.ROM originalQuery, ASTNode node, string message) 11 | : base(originalQuery, null!, message, node) 12 | { 13 | } 14 | 15 | /// 16 | public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.MethodNotAllowed; 17 | } 18 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/IHasPreferredStatusCode.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Defines an interface for errors that have a preferred HTTP status code. 5 | /// 6 | public interface IHasPreferredStatusCode 7 | { 8 | /// 9 | /// Returns the preferred HTTP status code for this error. 10 | /// 11 | HttpStatusCode PreferredStatusCode { get; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents an error indicating that the content-type is invalid, for example, could not be parsed or is not supported. 5 | /// 6 | public class InvalidContentTypeError : RequestError, IHasPreferredStatusCode 7 | { 8 | /// 9 | public InvalidContentTypeError() : base("Invalid 'Content-Type' header.") { } 10 | 11 | /// 12 | public InvalidContentTypeError(string message) : base("Invalid 'Content-Type' header: " + message) { } 13 | 14 | /// 15 | public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.UnsupportedMediaType; 16 | } 17 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/InvalidMapError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents an error when an invalid map path is provided in a GraphQL file upload request. 5 | /// 6 | public class InvalidMapError : RequestError 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public InvalidMapError(string message, Exception? innerException = null) 12 | : base("Invalid map path. " + message, innerException) 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/JsonInvalidError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents an error indicating that the JSON provided could not be parsed. 5 | /// 6 | public class JsonInvalidError : RequestError 7 | { 8 | /// 9 | public JsonInvalidError() : base($"JSON body text could not be parsed.") { } 10 | 11 | /// 12 | public JsonInvalidError(Exception innerException) : base($"JSON body text could not be parsed. {innerException.Message}") { } 13 | } 14 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Errors/WebSocketSubProtocolNotSupportedError.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.Errors; 2 | 3 | /// 4 | /// Represents an error indicating that none of the requested websocket sub-protocols are supported. 5 | /// 6 | public class WebSocketSubProtocolNotSupportedError : RequestError 7 | { 8 | /// 9 | public WebSocketSubProtocolNotSupportedError(IEnumerable requestedSubProtocols) 10 | : base($"Invalid requested WebSocket sub-protocol(s): {string.Join(",", requestedSubProtocols.Select(x => $"'{x}'"))}") 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/ExecutionResultActionResult.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore; 2 | 3 | /// 4 | /// An action result that formats the as JSON. 5 | /// 6 | public sealed class ExecutionResultActionResult : IActionResult 7 | { 8 | private readonly ExecutionResult _executionResult; 9 | private readonly HttpStatusCode _statusCode; 10 | 11 | /// 12 | public ExecutionResultActionResult(ExecutionResult executionResult) 13 | { 14 | _executionResult = executionResult; 15 | _statusCode = executionResult.Executed ? HttpStatusCode.OK : HttpStatusCode.BadRequest; 16 | } 17 | 18 | /// 19 | public ExecutionResultActionResult(ExecutionResult executionResult, HttpStatusCode statusCode) 20 | { 21 | _executionResult = executionResult; 22 | _statusCode = statusCode; 23 | } 24 | 25 | /// 26 | public string ContentType { get; set; } = GraphQLHttpMiddleware.CONTENTTYPE_GRAPHQLRESPONSEJSON; 27 | 28 | /// 29 | public async Task ExecuteResultAsync(ActionContext context) 30 | { 31 | var serializer = context.HttpContext.RequestServices.GetRequiredService(); 32 | var response = context.HttpContext.Response; 33 | response.ContentType = ContentType; 34 | response.StatusCode = (int)_statusCode; 35 | await serializer.WriteAsync(response.Body, _executionResult, context.HttpContext.RequestAborted); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/Extensions/GraphQLHttpRequestExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Utilities.Federation; 2 | 3 | /// 4 | /// extension methods for checking headers. 5 | /// 6 | public static class GraphQLHttpRequestExtensions 7 | { 8 | private const string HEADER_NAME = "apollo-federation-include-trace"; 9 | private const string HEADER_VALUE = "ftv1"; 10 | 11 | /// 12 | /// Determines if federated tracing is enabled through HTTP headers. 13 | /// 14 | /// if the 'apollo-federation-include-trace' HTTP header has a value of 'ftv1' 15 | public static bool IsApolloFederatedTracingEnabled(this HttpRequest request) 16 | { 17 | var headers = request?.Headers; 18 | if (headers != null && headers.TryGetValue(HEADER_NAME, out var values)) 19 | { 20 | var value = values.FirstOrDefault(); 21 | return HEADER_VALUE.Equals(value, StringComparison.OrdinalIgnoreCase); 22 | } 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/FormFileGraphType.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore; 2 | 3 | /// 4 | /// Represents a GraphQL scalar type named 'FormFile' for handling file uploads 5 | /// sent via multipart/form-data GraphQL requests. 6 | /// 7 | public class FormFileGraphType : ScalarGraphType 8 | { 9 | /// 10 | public override bool CanParseLiteral(GraphQLValue value) => value is GraphQLNullValue; 11 | 12 | /// 13 | public override object? ParseLiteral(GraphQLValue value) 14 | => value is GraphQLNullValue ? null : ThrowLiteralConversionError(value, "Uploaded files must be passed through variables."); 15 | 16 | /// 17 | public override bool CanParseValue(object? value) => value is IFormFile || value == null; 18 | 19 | /// 20 | public override object? ParseValue(object? value) => value switch 21 | { 22 | IFormFile _ => value, 23 | null => null, 24 | _ => ThrowValueConversionError(value) 25 | }; 26 | 27 | /// 28 | public override object? Serialize(object? value) => value is null ? null : 29 | throw new InvalidOperationException("The FormFile scalar graph type cannot be used to return information from a GraphQL endpoint."); 30 | 31 | /// 32 | public override bool IsValidDefault(object value) => false; 33 | 34 | /// 35 | public override GraphQLValue ToAST(object? value) => value is null ? new GraphQLNullValue() : 36 | throw new InvalidOperationException("FormFile values cannot be converted to an AST node."); 37 | } 38 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/HttpGetValidationRule.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore; 2 | 3 | /// 4 | /// Validates that HTTP GET requests only execute queries; not mutations or subscriptions. 5 | /// 6 | public sealed class HttpGetValidationRule : ValidationRuleBase 7 | { 8 | /// 9 | public override ValueTask GetPreNodeVisitorAsync(ValidationContext context) 10 | { 11 | if (context.Operation.Operation != OperationType.Query) 12 | { 13 | context.ReportError(new HttpMethodValidationError(context.Document.Source, context.Operation, "Only query operations allowed for GET requests.")); 14 | } 15 | return default; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/HttpPostValidationRule.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore; 2 | 3 | /// 4 | /// Validates that HTTP POST requests do not execute subscriptions. 5 | /// 6 | public sealed class HttpPostValidationRule : ValidationRuleBase 7 | { 8 | /// 9 | public override ValueTask GetPreNodeVisitorAsync(ValidationContext context) 10 | { 11 | if (context.Operation.Operation == OperationType.Subscription) 12 | { 13 | context.ReportError(new HttpMethodValidationError(context.Document.Source, context.Operation, "Subscription operations are not supported for POST requests.")); 14 | } 15 | return default; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/IAuthorizationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore; 2 | 3 | /// 4 | /// Provides authorization options. 5 | /// 6 | public interface IAuthorizationOptions 7 | { 8 | /// 9 | /// If set, requires that return 10 | /// for the user within 11 | /// prior to executing the GraphQL request or accepting the WebSocket connection. 12 | /// Technically this property should be named as AuthenticationRequired but for 13 | /// ASP.NET Core / GraphQL.NET naming and design decisions it was called so. 14 | /// 15 | bool AuthorizationRequired { get; } 16 | 17 | /// 18 | /// Requires that return 19 | /// for the user within 20 | /// for at least one role in the list prior to executing the GraphQL request or accepting 21 | /// the WebSocket connection. If no roles are specified, authorization is not checked. 22 | /// Also implies is enabled even if it is set to . 23 | /// 24 | IEnumerable AuthorizedRoles { get; } 25 | 26 | /// 27 | /// If set, requires that 28 | /// return a successful result for the user within 29 | /// for the specified policy before executing the GraphQL 30 | /// request or accepting the WebSocket connection. 31 | /// Also implies is enabled even if it is set to . 32 | /// 33 | string? AuthorizedPolicy { get; } 34 | } 35 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/IUserContextBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore; 2 | 3 | /// 4 | /// Creates a user context from a and/or the WebSocket initialization message's payload. 5 | ///

6 | /// The generated user context may be used for one or more GraphQL requests or 7 | /// subscriptions over the same HTTP connection. 8 | ///
9 | public interface IUserContextBuilder 10 | { 11 | /// 12 | /// The of the HTTP connection. 13 | /// 14 | /// The payload of the WebSocket connection request, if applicable. Typically this is either or 15 | /// an object that has not fully been deserialized yet; when using the Newtonsoft.Json deserializer, this will be a JObject, 16 | /// and when using System.Text.Json this will be a JsonElement. You may call 17 | /// to deserialize the node into the expected type. To deserialize 18 | /// into a nested set of IDictionary<string, object?> maps, call 19 | /// with as the generic type. 20 | ///

21 | /// To determine if this is a WebSocket connection request, check 22 | /// .WebSockets.IsWebSocketRequest. 23 | /// 24 | /// Dictionary object representing user context. Return to use default user context. 25 | ValueTask?> BuildUserContextAsync(HttpContext context, object? payload); 26 | } 27 | 28 | /// 29 | public interface IUserContextBuilder : IUserContextBuilder 30 | where TSchema : ISchema 31 | { 32 | } 33 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/UserContextBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore; 2 | 3 | /// 4 | /// Represents a user context builder based on a delegate. 5 | /// 6 | public class UserContextBuilder : IUserContextBuilder 7 | where TUserContext : IDictionary 8 | { 9 | private readonly Func?>> _func; 10 | 11 | /// 12 | /// Initializes a new instance with the specified delegate. 13 | /// 14 | public UserContextBuilder(Func> func) 15 | { 16 | if (func == null) 17 | throw new ArgumentNullException(nameof(func)); 18 | 19 | _func = async (context, _) => await func(context); 20 | } 21 | 22 | /// 23 | public UserContextBuilder(Func func) 24 | { 25 | if (func == null) 26 | throw new ArgumentNullException(nameof(func)); 27 | 28 | _func = (context, _) => new(func(context)); 29 | } 30 | 31 | /// 32 | public UserContextBuilder(Func> func) 33 | { 34 | if (func == null) 35 | throw new ArgumentNullException(nameof(func)); 36 | 37 | if (func is Func?>> func2) 38 | { 39 | _func = func2; 40 | } 41 | else 42 | { 43 | _func = async (context, payload) => await func(context, payload); 44 | } 45 | } 46 | 47 | /// 48 | public UserContextBuilder(Func func) 49 | { 50 | if (func == null) 51 | throw new ArgumentNullException(nameof(func)); 52 | 53 | _func = (context, payload) => new(func(context, payload)); 54 | } 55 | 56 | /// 57 | public ValueTask?> BuildUserContextAsync(HttpContext context, object? payload) 58 | => _func(context, payload); 59 | } 60 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/WebSockets/GraphQLWs/PingPayload.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWs; 2 | 3 | /// 4 | /// The payload of the ping message. 5 | /// 6 | public class PingPayload 7 | { 8 | /// 9 | /// The unique identifier of the ping message. 10 | /// 11 | public string? id { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/WebSockets/IOperationMessageProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.WebSockets; 2 | 3 | /// 4 | /// Processes a stream of messages received from a WebSockets client. 5 | /// must be called when the WebSocket connection 6 | /// has received a close message, and/or when the connection terminates. 7 | /// Methods defined within this interface need not be thread-safe. 8 | /// 9 | public interface IOperationMessageProcessor : IDisposable 10 | { 11 | /// 12 | /// Starts the connection initialization timer, if configured. 13 | /// 14 | Task InitializeConnectionAsync(); 15 | 16 | /// 17 | /// Called when a message is received from the client. 18 | /// 19 | Task OnMessageReceivedAsync(OperationMessage message); 20 | } 21 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/WebSockets/IWebSocketAuthenticationService.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.WebSockets; 2 | 3 | /// 4 | /// Authenticates an incoming GraphQL over WebSockets request with the 5 | /// connection initialization message. A typical implementation will 6 | /// set the property after reading the 7 | /// authorization token. This service must be registered as a singleton 8 | /// in the dependency injection framework. 9 | /// 10 | public interface IWebSocketAuthenticationService 11 | { 12 | /// 13 | /// Authenticates an incoming GraphQL over WebSockets request with the connection initialization message. The implementation should 14 | /// set the .HttpContext.User 15 | /// property after validating the provided credentials. 16 | ///

17 | /// After calling this method to authenticate the request, the infrastructure will authorize the incoming request via the 18 | /// , and 19 | /// properties. 20 | ///
21 | Task AuthenticateAsync(IWebSocketConnection connection, string subProtocol, OperationMessage operationMessage); 22 | } 23 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/WebSockets/IWebSocketConnection.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.WebSockets; 2 | 3 | /// 4 | /// Represents a WebSocket connection, dispatching received messages over 5 | /// the connection to the specified , 6 | /// and sending requested messages out the connection when requested. 7 | ///

8 | /// may be only called once. 9 | /// All members must be thread-safe. 10 | ///
11 | public interface IWebSocketConnection : IDisposable 12 | { 13 | /// 14 | /// Listens to incoming messages over the WebSocket connection, 15 | /// dispatching the messages to the specified . 16 | /// Returns or throws when the WebSocket connection is closed. 17 | /// 18 | Task ExecuteAsync(IOperationMessageProcessor operationMessageProcessor); 19 | 20 | /// 21 | /// Sends a message to the client. 22 | /// 23 | Task SendMessageAsync(OperationMessage message); 24 | 25 | /// 26 | /// Performs a graceful shutdown of the WebSocket connection with event ID 1000, and 27 | /// prevents further incoming messages from being dispatched through . 28 | /// 29 | Task CloseAsync(); 30 | 31 | /// 32 | /// Performs a graceful shutdown of the WebSocket connection with the specified error information, and 33 | /// prevents further incoming messages from being dispatched through . 34 | /// 35 | Task CloseAsync(int eventId, string? description); 36 | 37 | /// 38 | /// Returns the last UTC time that a message was sent to the client. 39 | /// This property is needed to implement the idle timeout to only send a keep-alive message after ## seconds of "no traffic". 40 | /// 41 | DateTime LastMessageSentAt { get; } 42 | 43 | /// 44 | CancellationToken RequestAborted { get; } 45 | 46 | /// 47 | /// Returns the associated with this request. 48 | /// 49 | HttpContext HttpContext { get; } 50 | } 51 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/WebSockets/KeepAliveMode.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.WebSockets; 2 | 3 | /// 4 | /// Specifies the mode of keep-alive behavior. 5 | /// 6 | public enum KeepAliveMode 7 | { 8 | /// 9 | /// Same as : Sends a unidirectional keep-alive message when no message has been received within the specified timeout period. 10 | /// 11 | Default = 0, 12 | 13 | /// 14 | /// Sends a unidirectional keep-alive message when no message has been received within the specified timeout period. 15 | /// 16 | Timeout = 1, 17 | 18 | /// 19 | /// Sends a unidirectional keep-alive message at a fixed interval, regardless of message activity. 20 | /// 21 | Interval = 2, 22 | 23 | /// 24 | /// Sends a Ping message with a payload after the specified timeout from the last received Pong, 25 | /// and waits for a corresponding Pong response. Requires that the client reflects the payload 26 | /// in the response. Forcibly disconnects the client if the client does not respond with a Pong 27 | /// message within the specified timeout. This means that a dead connection will be closed after 28 | /// a maximum of double the period. 29 | /// 30 | /// 31 | /// This mode is particularly useful when backpressure causes subscription messages to be delayed 32 | /// due to a slow or unresponsive client connection. The server can detect that the client is not 33 | /// processing messages in a timely manner and disconnect the client to free up resources. 34 | /// 35 | TimeoutWithPayload = 3, 36 | } 37 | -------------------------------------------------------------------------------- /src/Transports.AspNetCore/WebSockets/WebSocketWriterStream.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Transports.AspNetCore.WebSockets; 2 | 3 | internal class WebSocketWriterStream : Stream 4 | { 5 | private readonly WebSocket _webSocket; 6 | 7 | public WebSocketWriterStream(WebSocket webSocket) 8 | { 9 | _webSocket = webSocket; 10 | } 11 | 12 | public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 13 | => _webSocket.SendAsync(new ArraySegment(buffer, offset, count), WebSocketMessageType.Text, false, cancellationToken); 14 | 15 | #if !NETSTANDARD2_0 16 | public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) 17 | => _webSocket.SendAsync(buffer, WebSocketMessageType.Text, false, cancellationToken); 18 | #endif 19 | 20 | public override void Write(byte[] buffer, int offset, int count) => WriteAsync(buffer, offset, count).GetAwaiter().GetResult(); 21 | 22 | public override Task FlushAsync(CancellationToken cancellationToken) 23 | => _webSocket.SendAsync(new ArraySegment(Array.Empty()), WebSocketMessageType.Text, true, cancellationToken); 24 | 25 | public override void Flush() => FlushAsync().GetAwaiter().GetResult(); 26 | 27 | public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); 28 | 29 | public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); 30 | 31 | public override void SetLength(long value) => throw new NotSupportedException(); 32 | 33 | public override bool CanRead => false; 34 | public override bool CanSeek => false; 35 | public override bool CanWrite => true; 36 | 37 | public override long Length => throw new NotSupportedException(); 38 | 39 | public override long Position 40 | { 41 | get => throw new NotSupportedException(); 42 | set => throw new NotSupportedException(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Ui.Altair/AltairActionResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace GraphQL.Server.Ui.Altair; 4 | 5 | /// 6 | /// An action result that returns a Altair user interface. 7 | /// 8 | public class AltairActionResult : IActionResult 9 | { 10 | private readonly AltairMiddleware _middleware; 11 | 12 | /// 13 | /// Initializes the Altair action result with the specified options. 14 | /// 15 | public AltairActionResult(AltairOptions options) 16 | { 17 | _middleware = new(_ => Task.CompletedTask, options); 18 | } 19 | 20 | /// 21 | /// Initializes the Altair action result with the specified optional configuration delegate. 22 | /// 23 | public AltairActionResult(Action? configure = null) 24 | { 25 | var options = new AltairOptions(); 26 | configure?.Invoke(options); 27 | _middleware = new(_ => Task.CompletedTask, options); 28 | } 29 | 30 | /// 31 | public Task ExecuteResultAsync(ActionContext context) 32 | => _middleware.Invoke(context.HttpContext); 33 | } 34 | -------------------------------------------------------------------------------- /src/Ui.Altair/AltairMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using GraphQL.Server.Ui.Altair.Internal; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace GraphQL.Server.Ui.Altair; 6 | 7 | /// 8 | /// A middleware for Altair GraphQL UI. 9 | /// 10 | public class AltairMiddleware 11 | { 12 | private readonly AltairOptions _options; 13 | 14 | /// 15 | /// The page model used to render Altair. 16 | /// 17 | private AltairPageModel? _pageModel; 18 | 19 | /// 20 | /// Create a new 21 | /// 22 | /// The next request delegate; not used, this is a terminal middleware. 23 | /// Options to customize middleware 24 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "ASP.NET Core conventions")] 25 | public AltairMiddleware(RequestDelegate next, AltairOptions options) 26 | { 27 | _options = options ?? throw new ArgumentNullException(nameof(options)); 28 | } 29 | 30 | /// 31 | /// Try to execute the logic of the middleware 32 | /// 33 | /// The HttpContext 34 | public Task Invoke(HttpContext httpContext) 35 | { 36 | if (httpContext == null) 37 | throw new ArgumentNullException(nameof(httpContext)); 38 | 39 | httpContext.Response.ContentType = "text/html"; 40 | httpContext.Response.StatusCode = 200; 41 | 42 | _pageModel ??= new AltairPageModel(_options); 43 | 44 | byte[] data = Encoding.UTF8.GetBytes(_pageModel.Render()); 45 | return httpContext.Response.Body.WriteAsync(data, 0, data.Length); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Ui.Altair/AltairOptions.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Ui.Altair; 2 | 3 | /// 4 | /// Options to customize . 5 | /// 6 | public class AltairOptions 7 | { 8 | /// 9 | /// The GraphQL EndPoint. 10 | /// 11 | public string GraphQLEndPoint { get; set; } = "/graphql"; 12 | 13 | /// 14 | /// Subscriptions EndPoint. 15 | /// 16 | public string SubscriptionsEndPoint { get; set; } = "/graphql"; 17 | 18 | /// 19 | /// Altair headers configuration. 20 | /// 21 | public Dictionary? Headers { get; set; } 22 | 23 | /// 24 | /// Subscriptions payload. 25 | /// 26 | public Dictionary? SubscriptionsPayload { get; set; } 27 | 28 | /// 29 | /// Altair UI settings. 30 | /// Available settings 31 | /// 32 | public Dictionary? Settings { get; set; } 33 | 34 | /// 35 | /// Gets or sets a Stream function for retrieving the Altair GraphQL UI page. 36 | /// 37 | public Func IndexStream { get; set; } = _ => typeof(AltairOptions).Assembly 38 | .GetManifestResourceStream("GraphQL.Server.Ui.Altair.Internal.altair.cshtml")!; 39 | 40 | /// 41 | /// Gets or sets a delegate that is called after all transformations of the Altair GraphQL UI page. 42 | /// 43 | public Func PostConfigure { get; set; } = (options, result) => result; 44 | } 45 | -------------------------------------------------------------------------------- /src/Ui.Altair/Extensions/AltairApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Server.Ui.Altair; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace Microsoft.AspNetCore.Builder; 5 | 6 | /// 7 | /// Extensions for to add in the HTTP request pipeline. 8 | /// 9 | public static class AltairApplicationBuilderExtensions 10 | { 11 | /// Adds middleware for Altair GraphQL UI using the specified options. 12 | /// to configure an application's request pipeline. 13 | /// Options to customize . If not set, then the default values will be used. 14 | /// The path to the Altair GraphQL UI endpoint which defaults to '/ui/altair' 15 | /// The reference to provided instance. 16 | public static IApplicationBuilder UseGraphQLAltair(this IApplicationBuilder app, string path = "/ui/altair", AltairOptions? options = null) 17 | { 18 | return app.UseWhen( 19 | context => HttpMethods.IsGet(context.Request.Method) && !context.WebSockets.IsWebSocketRequest && 20 | context.Request.Path.StartsWithSegments(path, out var remaining) && string.IsNullOrEmpty(remaining), 21 | b => b.UseMiddleware(options ?? new AltairOptions())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ui.Altair/Extensions/AltairEndpointRouteBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !NETSTANDARD2_0 2 | 3 | using GraphQL.Server.Ui.Altair; 4 | using Microsoft.AspNetCore.Routing; 5 | 6 | namespace Microsoft.AspNetCore.Builder; 7 | 8 | /// 9 | /// Extensions for to add in the HTTP request pipeline. 10 | /// 11 | public static class AltairEndpointRouteBuilderExtensions 12 | { 13 | /// 14 | /// Add the Altair middleware to the HTTP request pipeline 15 | /// 16 | /// Defines a contract for a route builder in an application. A route builder specifies the routes for an application. 17 | /// Options to customize . If not set, then the default values will be used. 18 | /// The route pattern. 19 | /// The received as parameter 20 | public static AltairEndpointConventionBuilder MapGraphQLAltair(this IEndpointRouteBuilder endpoints, string pattern = "ui/altair", AltairOptions? options = null) 21 | { 22 | if (endpoints == null) 23 | throw new ArgumentNullException(nameof(endpoints)); 24 | 25 | var requestDelegate = endpoints.CreateApplicationBuilder().UseMiddleware(options ?? new AltairOptions()).Build(); 26 | return new AltairEndpointConventionBuilder(endpoints.MapGet(pattern, requestDelegate).WithDisplayName("GraphQL Altair")); 27 | } 28 | } 29 | 30 | /// 31 | /// Builds conventions that will be used for customization of Microsoft.AspNetCore.Builder.EndpointBuilder instances. 32 | /// Special convention builder that allows you to write specific extension methods for ASP.NET Core routing subsystem. 33 | /// 34 | public class AltairEndpointConventionBuilder : IEndpointConventionBuilder 35 | { 36 | private readonly IEndpointConventionBuilder _builder; 37 | 38 | internal AltairEndpointConventionBuilder(IEndpointConventionBuilder builder) 39 | { 40 | _builder = builder; 41 | } 42 | 43 | /// 44 | public void Add(Action convention) => _builder.Add(convention); 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/Ui.Altair/Ui.Altair.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1;netstandard2.0;net8.0 5 | GraphQL Altair integration for ASP.NET Core 6 | Altair;GraphQL 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Ui.GraphiQL/Extensions/GraphiQLApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Server.Ui.GraphiQL; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace Microsoft.AspNetCore.Builder; 5 | 6 | /// 7 | /// Extensions for to add in the HTTP request pipeline. 8 | /// 9 | public static class GraphiQLApplicationBuilderExtensions 10 | { 11 | /// Adds middleware for GraphiQL using the specified options. 12 | /// to configure an application's request pipeline. 13 | /// Options to customize . If not set, then the default values will be used. 14 | /// The path to the GraphiQL endpoint which defaults to '/ui/graphiql' 15 | /// The reference to provided instance. 16 | public static IApplicationBuilder UseGraphQLGraphiQL(this IApplicationBuilder app, string path = "/ui/graphiql", GraphiQLOptions? options = null) 17 | { 18 | return app.UseWhen( 19 | context => HttpMethods.IsGet(context.Request.Method) && !context.WebSockets.IsWebSocketRequest && 20 | context.Request.Path.StartsWithSegments(path, out var remaining) && string.IsNullOrEmpty(remaining), 21 | b => b.UseMiddleware(options ?? new GraphiQLOptions())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ui.GraphiQL/Extensions/GraphiQLEndpointRouteBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !NETSTANDARD2_0 2 | 3 | using GraphQL.Server.Ui.GraphiQL; 4 | using Microsoft.AspNetCore.Routing; 5 | 6 | namespace Microsoft.AspNetCore.Builder; 7 | 8 | /// 9 | /// Extensions for to add in the HTTP request pipeline. 10 | /// 11 | public static class GraphiQLEndpointRouteBuilderExtensions 12 | { 13 | /// 14 | /// Add the GraphiQL middleware to the HTTP request pipeline 15 | /// 16 | /// Defines a contract for a route builder in an application. A route builder specifies the routes for an application. 17 | /// Options to customize . If not set, then the default values will be used. 18 | /// The route pattern. 19 | /// The received as parameter 20 | public static GraphiQLEndpointConventionBuilder MapGraphQLGraphiQL(this IEndpointRouteBuilder endpoints, string pattern = "ui/graphiql", GraphiQLOptions? options = null) 21 | { 22 | if (endpoints == null) 23 | throw new ArgumentNullException(nameof(endpoints)); 24 | 25 | var requestDelegate = endpoints.CreateApplicationBuilder().UseMiddleware(options ?? new GraphiQLOptions()).Build(); 26 | return new GraphiQLEndpointConventionBuilder(endpoints.MapGet(pattern, requestDelegate).WithDisplayName("GraphiQL")); 27 | } 28 | } 29 | 30 | /// 31 | /// Builds conventions that will be used for customization of Microsoft.AspNetCore.Builder.EndpointBuilder instances. 32 | /// Special convention builder that allows you to write specific extension methods for ASP.NET Core routing subsystem. 33 | /// 34 | public class GraphiQLEndpointConventionBuilder : IEndpointConventionBuilder 35 | { 36 | private readonly IEndpointConventionBuilder _builder; 37 | 38 | internal GraphiQLEndpointConventionBuilder(IEndpointConventionBuilder builder) 39 | { 40 | _builder = builder; 41 | } 42 | 43 | /// 44 | public void Add(Action convention) => _builder.Add(convention); 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/Ui.GraphiQL/GraphiQLActionResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace GraphQL.Server.Ui.GraphiQL; 4 | 5 | /// 6 | /// An action result that returns a GraphiQL user interface. 7 | /// 8 | public class GraphiQLActionResult : IActionResult 9 | { 10 | private readonly GraphiQLMiddleware _middleware; 11 | 12 | /// 13 | /// Initializes the GraphiQL action result with the specified options. 14 | /// 15 | public GraphiQLActionResult(GraphiQLOptions options) 16 | { 17 | _middleware = new(_ => Task.CompletedTask, options); 18 | } 19 | 20 | /// 21 | /// Initializes the GraphiQL action result with the specified optional configuration delegate. 22 | /// 23 | public GraphiQLActionResult(Action? configure = null) 24 | { 25 | var options = new GraphiQLOptions(); 26 | configure?.Invoke(options); 27 | _middleware = new(_ => Task.CompletedTask, options); 28 | } 29 | 30 | /// 31 | public Task ExecuteResultAsync(ActionContext context) 32 | => _middleware.Invoke(context.HttpContext); 33 | } 34 | -------------------------------------------------------------------------------- /src/Ui.GraphiQL/GraphiQLMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using GraphQL.Server.Ui.GraphiQL.Internal; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace GraphQL.Server.Ui.GraphiQL; 6 | 7 | /// 8 | /// A middleware for GraphiQL UI. 9 | /// 10 | public class GraphiQLMiddleware 11 | { 12 | private readonly GraphiQLOptions _options; 13 | 14 | /// 15 | /// The page model used to render GraphiQL. 16 | /// 17 | private GraphiQLPageModel? _pageModel; 18 | 19 | /// 20 | /// Create a new 21 | /// 22 | /// The next request delegate; not used, this is a terminal middleware. 23 | /// Options to customize middleware 24 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "ASP.NET Core conventions")] 25 | public GraphiQLMiddleware(RequestDelegate next, GraphiQLOptions options) 26 | { 27 | _options = options ?? throw new ArgumentNullException(nameof(options)); 28 | } 29 | 30 | /// 31 | /// Try to execute the logic of the middleware 32 | /// 33 | /// The HttpContext 34 | /// 35 | public Task Invoke(HttpContext httpContext) 36 | { 37 | if (httpContext == null) 38 | throw new ArgumentNullException(nameof(httpContext)); 39 | 40 | httpContext.Response.ContentType = "text/html"; 41 | httpContext.Response.StatusCode = 200; 42 | 43 | _pageModel ??= new GraphiQLPageModel(_options); 44 | 45 | byte[] data = Encoding.UTF8.GetBytes(_pageModel.Render()); 46 | return httpContext.Response.Body.WriteAsync(data, 0, data.Length); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Ui.GraphiQL/RequestCredentials.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Ui.GraphiQL; 2 | 3 | /// 4 | /// Indicates whether the user agent should send cookies from the other domain 5 | /// in the case of cross-origin requests. 6 | /// 7 | public enum RequestCredentials 8 | { 9 | /// 10 | /// Never send or receive cookies. 11 | /// 12 | Omit, 13 | 14 | /// 15 | /// Always send user credentials (cookies, basic http auth, etc..), even for cross-origin calls. 16 | /// 17 | Include, 18 | 19 | /// 20 | /// Send user credentials (cookies, basic http auth, etc..) if the URL is on the same origin as the calling script. 21 | /// 22 | SameOrigin 23 | } 24 | -------------------------------------------------------------------------------- /src/Ui.GraphiQL/Ui.GraphiQL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1;netstandard2.0;net8.0 5 | GraphiQL integration for ASP.NET Core 6 | GraphiQL;GraphQL 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Ui.Playground/Extensions/PlaygroundApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Server.Ui.Playground; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace Microsoft.AspNetCore.Builder; 5 | 6 | /// 7 | /// Extensions for to add in the HTTP request pipeline. 8 | /// 9 | public static class PlaygroundApplicationBuilderExtensions 10 | { 11 | /// Adds middleware for GraphQL Playground using the specified options. 12 | /// to configure an application's request pipeline. 13 | /// Options to customize . If not set, then the default values will be used. 14 | /// The path to the GraphQL Playground endpoint which defaults to '/ui/playground' 15 | /// The reference to provided instance. 16 | [Obsolete("GraphQL Playground has not been updated since 2019. Please use GraphiQL instead.")] 17 | public static IApplicationBuilder UseGraphQLPlayground(this IApplicationBuilder app, string path = "/ui/playground", PlaygroundOptions? options = null) 18 | { 19 | return app.UseWhen( 20 | context => HttpMethods.IsGet(context.Request.Method) && !context.WebSockets.IsWebSocketRequest && 21 | context.Request.Path.StartsWithSegments(path, out var remaining) && string.IsNullOrEmpty(remaining), 22 | b => b.UseMiddleware(options ?? new PlaygroundOptions())); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Ui.Playground/Extensions/PlaygroundEndpointRouteBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !NETSTANDARD2_0 2 | 3 | using GraphQL.Server.Ui.Playground; 4 | using Microsoft.AspNetCore.Routing; 5 | 6 | namespace Microsoft.AspNetCore.Builder; 7 | 8 | /// 9 | /// Extensions for to add in the HTTP request pipeline. 10 | /// 11 | public static class PlaygroundEndpointRouteBuilderExtensions 12 | { 13 | /// 14 | /// Add the Playground middleware to the HTTP request pipeline 15 | /// 16 | /// Defines a contract for a route builder in an application. A route builder specifies the routes for an application. 17 | /// Options to customize . If not set, then the default values will be used. 18 | /// The route pattern. 19 | /// The received as parameter 20 | [Obsolete("GraphQL Playground has not been updated since 2019. Please use GraphiQL instead.")] 21 | public static PlaygroundEndpointConventionBuilder MapGraphQLPlayground(this IEndpointRouteBuilder endpoints, string pattern = "ui/playground", PlaygroundOptions? options = null) 22 | { 23 | if (endpoints == null) 24 | throw new ArgumentNullException(nameof(endpoints)); 25 | 26 | var requestDelegate = endpoints.CreateApplicationBuilder().UseMiddleware(options ?? new PlaygroundOptions()).Build(); 27 | return new PlaygroundEndpointConventionBuilder(endpoints.MapGet(pattern, requestDelegate).WithDisplayName("GraphQL Playground")); 28 | } 29 | } 30 | 31 | /// 32 | /// Builds conventions that will be used for customization of Microsoft.AspNetCore.Builder.EndpointBuilder instances. 33 | /// Special convention builder that allows you to write specific extension methods for ASP.NET Core routing subsystem. 34 | /// 35 | public class PlaygroundEndpointConventionBuilder : IEndpointConventionBuilder 36 | { 37 | private readonly IEndpointConventionBuilder _builder; 38 | 39 | internal PlaygroundEndpointConventionBuilder(IEndpointConventionBuilder builder) 40 | { 41 | _builder = builder; 42 | } 43 | 44 | /// 45 | public void Add(Action convention) => _builder.Add(convention); 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/Ui.Playground/PlaygroundActionResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace GraphQL.Server.Ui.Playground; 4 | 5 | /// 6 | /// An action result that returns a Playground user interface. 7 | /// 8 | [Obsolete("GraphQL Playground has not been updated since 2019. Please use GraphiQL instead.")] 9 | public class PlaygroundActionResult : IActionResult 10 | { 11 | private readonly PlaygroundMiddleware _middleware; 12 | 13 | /// 14 | /// Initializes the Playground action result with the specified options. 15 | /// 16 | public PlaygroundActionResult(PlaygroundOptions options) 17 | { 18 | _middleware = new(_ => Task.CompletedTask, options); 19 | } 20 | 21 | /// 22 | /// Initializes the Playground action result with the specified optional configuration delegate. 23 | /// 24 | public PlaygroundActionResult(Action? configure = null) 25 | { 26 | var options = new PlaygroundOptions(); 27 | configure?.Invoke(options); 28 | _middleware = new(_ => Task.CompletedTask, options); 29 | } 30 | 31 | /// 32 | public Task ExecuteResultAsync(ActionContext context) 33 | => _middleware.Invoke(context.HttpContext); 34 | } 35 | -------------------------------------------------------------------------------- /src/Ui.Playground/PlaygroundMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using GraphQL.Server.Ui.Playground.Internal; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace GraphQL.Server.Ui.Playground; 6 | 7 | /// 8 | /// A middleware for GraphQL Playground UI. 9 | /// 10 | [Obsolete("GraphQL Playground has not been updated since 2019. Please use GraphiQL instead.")] 11 | public class PlaygroundMiddleware 12 | { 13 | private readonly PlaygroundOptions _options; 14 | 15 | /// 16 | /// The page model used to render Playground. 17 | /// 18 | private PlaygroundPageModel? _pageModel; 19 | 20 | /// 21 | /// Create a new 22 | /// 23 | /// The next request delegate; not used, this is a terminal middleware. 24 | /// Options to customize middleware 25 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "ASP.NET Core conventions")] 26 | public PlaygroundMiddleware(RequestDelegate next, PlaygroundOptions options) 27 | { 28 | _options = options ?? throw new ArgumentNullException(nameof(options)); 29 | } 30 | 31 | /// 32 | /// Try to execute the logic of the middleware 33 | /// 34 | /// The HttpContext 35 | public Task Invoke(HttpContext httpContext) 36 | { 37 | if (httpContext == null) 38 | throw new ArgumentNullException(nameof(httpContext)); 39 | 40 | httpContext.Response.ContentType = "text/html"; 41 | httpContext.Response.StatusCode = 200; 42 | 43 | _pageModel ??= new PlaygroundPageModel(_options); 44 | 45 | byte[] data = Encoding.UTF8.GetBytes(_pageModel.Render()); 46 | return httpContext.Response.Body.WriteAsync(data, 0, data.Length); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Ui.Playground/RequestCredentials.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Ui.Playground; 2 | 3 | /// 4 | /// Indicates whether the user agent should send cookies from the other domain 5 | /// in the case of cross-origin requests. 6 | /// 7 | public enum RequestCredentials 8 | { 9 | /// 10 | /// Never send or receive cookies. 11 | /// 12 | Omit, 13 | 14 | /// 15 | /// Always send user credentials (cookies, basic http auth, etc..), even for cross-origin calls. 16 | /// 17 | Include, 18 | 19 | /// 20 | /// Send user credentials (cookies, basic http auth, etc..) if the URL is on the same origin as the calling script. 21 | /// 22 | SameOrigin 23 | } 24 | -------------------------------------------------------------------------------- /src/Ui.Playground/Ui.Playground.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1;netstandard2.0;net8.0 5 | GraphQL Playground integration for ASP.NET Core 6 | GraphQL;Playground 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Ui.Voyager/Extensions/VoyagerApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Server.Ui.Voyager; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace Microsoft.AspNetCore.Builder; 5 | 6 | /// 7 | /// Extensions for to add in the HTTP request pipeline. 8 | /// 9 | public static class VoyagerApplicationBuilderExtensions 10 | { 11 | /// Adds middleware for Voyager using the specified options. 12 | /// to configure an application's request pipeline. 13 | /// Options to customize . If not set, then the default values will be used. 14 | /// The path to the Voyager endpoint which defaults to '/ui/voyager' 15 | /// The reference to provided instance. 16 | public static IApplicationBuilder UseGraphQLVoyager(this IApplicationBuilder app, string path = "/ui/voyager", VoyagerOptions? options = null) 17 | { 18 | return app.UseWhen( 19 | context => HttpMethods.IsGet(context.Request.Method) && !context.WebSockets.IsWebSocketRequest && 20 | context.Request.Path.StartsWithSegments(path, out var remaining) && string.IsNullOrEmpty(remaining), 21 | b => b.UseMiddleware(options ?? new VoyagerOptions())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ui.Voyager/Extensions/VoyagerEndpointRouteBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !NETSTANDARD2_0 2 | 3 | using GraphQL.Server.Ui.Voyager; 4 | using Microsoft.AspNetCore.Routing; 5 | 6 | namespace Microsoft.AspNetCore.Builder; 7 | 8 | /// 9 | /// Extensions for to add in the HTTP request pipeline. 10 | /// 11 | public static class VoyagerEndpointRouteBuilderExtensions 12 | { 13 | /// 14 | /// Add the Voyager middleware to the HTTP request pipeline 15 | /// 16 | /// Defines a contract for a route builder in an application. A route builder specifies the routes for an application. 17 | /// Options to customize . If not set, then the default values will be used. 18 | /// The route pattern. 19 | /// The received as parameter 20 | public static VoyagerEndpointConventionBuilder MapGraphQLVoyager(this IEndpointRouteBuilder endpoints, string pattern = "ui/voyager", VoyagerOptions? options = null) 21 | { 22 | if (endpoints == null) 23 | throw new ArgumentNullException(nameof(endpoints)); 24 | 25 | var requestDelegate = endpoints.CreateApplicationBuilder().UseMiddleware(options ?? new VoyagerOptions()).Build(); 26 | return new VoyagerEndpointConventionBuilder(endpoints.MapGet(pattern, requestDelegate).WithDisplayName("GraphQL Voyager")); 27 | } 28 | } 29 | 30 | /// 31 | /// Builds conventions that will be used for customization of Microsoft.AspNetCore.Builder.EndpointBuilder instances. 32 | /// Special convention builder that allows you to write specific extension methods for ASP.NET Core routing subsystem. 33 | /// 34 | public class VoyagerEndpointConventionBuilder : IEndpointConventionBuilder 35 | { 36 | private readonly IEndpointConventionBuilder _builder; 37 | 38 | internal VoyagerEndpointConventionBuilder(IEndpointConventionBuilder builder) 39 | { 40 | _builder = builder; 41 | } 42 | 43 | /// 44 | public void Add(Action convention) => _builder.Add(convention); 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/Ui.Voyager/RequestCredentials.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Ui.Voyager; 2 | 3 | /// 4 | /// Indicates whether the user agent should send cookies from the other domain 5 | /// in the case of cross-origin requests. 6 | /// 7 | public enum RequestCredentials 8 | { 9 | /// 10 | /// Never send or receive cookies. 11 | /// 12 | Omit, 13 | 14 | /// 15 | /// Always send user credentials (cookies, basic http auth, etc..), even for cross-origin calls. 16 | /// 17 | Include, 18 | 19 | /// 20 | /// Send user credentials (cookies, basic http auth, etc..) if the URL is on the same origin as the calling script. 21 | /// 22 | SameOrigin 23 | } 24 | -------------------------------------------------------------------------------- /src/Ui.Voyager/Ui.Voyager.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1;netstandard2.0;net8.0 5 | GraphQL Voyager integration for ASP.NET Core 6 | GraphQL;Voyager 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Ui.Voyager/VoyagerActionResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace GraphQL.Server.Ui.Voyager; 4 | 5 | /// 6 | /// An action result that returns a Voyager user interface. 7 | /// 8 | public class VoyagerActionResult : IActionResult 9 | { 10 | private readonly VoyagerMiddleware _middleware; 11 | 12 | /// 13 | /// Initializes the Voyager action result with the specified options. 14 | /// 15 | public VoyagerActionResult(VoyagerOptions options) 16 | { 17 | _middleware = new(_ => Task.CompletedTask, options); 18 | } 19 | 20 | /// 21 | /// Initializes the Voyager action result with the specified optional configuration delegate. 22 | /// 23 | public VoyagerActionResult(Action? configure = null) 24 | { 25 | var options = new VoyagerOptions(); 26 | configure?.Invoke(options); 27 | _middleware = new(_ => Task.CompletedTask, options); 28 | } 29 | 30 | /// 31 | public Task ExecuteResultAsync(ActionContext context) 32 | => _middleware.Invoke(context.HttpContext); 33 | } 34 | -------------------------------------------------------------------------------- /src/Ui.Voyager/VoyagerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using GraphQL.Server.Ui.Voyager.Internal; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace GraphQL.Server.Ui.Voyager; 6 | 7 | /// 8 | /// A middleware for Voyager UI. 9 | /// 10 | public class VoyagerMiddleware 11 | { 12 | private readonly VoyagerOptions _options; 13 | 14 | /// 15 | /// The page model used to render Voyager. 16 | /// 17 | private VoyagerPageModel? _pageModel; 18 | 19 | /// 20 | /// Create a new 21 | /// 22 | /// The next request delegate; not used, this is a terminal middleware. 23 | /// Options to customize middleware 24 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "ASP.NET Core conventions")] 25 | public VoyagerMiddleware(RequestDelegate next, VoyagerOptions options) 26 | { 27 | _options = options ?? throw new ArgumentNullException(nameof(options)); 28 | } 29 | 30 | /// 31 | /// Try to execute the logic of the middleware 32 | /// 33 | /// The HttpContext 34 | /// 35 | public Task Invoke(HttpContext httpContext) 36 | { 37 | if (httpContext == null) 38 | throw new ArgumentNullException(nameof(httpContext)); 39 | 40 | httpContext.Response.ContentType = "text/html"; 41 | httpContext.Response.StatusCode = 200; 42 | 43 | _pageModel ??= new VoyagerPageModel(_options); 44 | 45 | byte[] data = Encoding.UTF8.GetBytes(_pageModel.Render()); 46 | return httpContext.Response.Body.WriteAsync(data, 0, data.Length); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Ui.Voyager/VoyagerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Ui.Voyager; 2 | 3 | /// 4 | /// Options to customize . 5 | /// 6 | public class VoyagerOptions 7 | { 8 | /// 9 | /// The GraphQL EndPoint. 10 | /// 11 | public string GraphQLEndPoint { get; set; } = "/graphql"; 12 | 13 | /// 14 | /// HTTP headers with which the Voyager will send introspection query. 15 | /// 16 | public Dictionary? Headers { get; set; } 17 | 18 | /// 19 | /// Gets or sets a Stream function for retrieving the Voyager UI page. 20 | /// 21 | public Func IndexStream { get; set; } = _ => typeof(VoyagerOptions).Assembly 22 | .GetManifestResourceStream("GraphQL.Server.Ui.Voyager.Internal.voyager.cshtml")!; 23 | 24 | /// 25 | /// Gets or sets a delegate that is called after all transformations of the Voyager UI page. 26 | /// 27 | public Func PostConfigure { get; set; } = (options, result) => result; 28 | 29 | /// 30 | /// Indicates whether the user agent should send cookies from the other domain 31 | /// in the case of cross-origin requests. 32 | /// 33 | /// 34 | /// See . 35 | /// 36 | public RequestCredentials RequestCredentials { get; set; } = RequestCredentials.SameOrigin; 37 | } 38 | -------------------------------------------------------------------------------- /tests/ApiApprovalTests/ApiApprovalTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/ApiApprovalTests/net80+netcoreapp31/GraphQL.Server.Ui.Voyager.approved.txt: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Ui.Voyager 2 | { 3 | public enum RequestCredentials 4 | { 5 | Omit = 0, 6 | Include = 1, 7 | SameOrigin = 2, 8 | } 9 | public class VoyagerActionResult : Microsoft.AspNetCore.Mvc.IActionResult 10 | { 11 | public VoyagerActionResult(GraphQL.Server.Ui.Voyager.VoyagerOptions options) { } 12 | public VoyagerActionResult(System.Action? configure = null) { } 13 | public System.Threading.Tasks.Task ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) { } 14 | } 15 | public class VoyagerMiddleware 16 | { 17 | public VoyagerMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.Server.Ui.Voyager.VoyagerOptions options) { } 18 | public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext httpContext) { } 19 | } 20 | public class VoyagerOptions 21 | { 22 | public VoyagerOptions() { } 23 | public string GraphQLEndPoint { get; set; } 24 | public System.Collections.Generic.Dictionary? Headers { get; set; } 25 | public System.Func IndexStream { get; set; } 26 | public System.Func PostConfigure { get; set; } 27 | public GraphQL.Server.Ui.Voyager.RequestCredentials RequestCredentials { get; set; } 28 | } 29 | } 30 | namespace Microsoft.AspNetCore.Builder 31 | { 32 | public static class VoyagerApplicationBuilderExtensions 33 | { 34 | public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQLVoyager(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, string path = "/ui/voyager", GraphQL.Server.Ui.Voyager.VoyagerOptions? options = null) { } 35 | } 36 | public class VoyagerEndpointConventionBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder 37 | { 38 | public void Add(System.Action convention) { } 39 | } 40 | public static class VoyagerEndpointRouteBuilderExtensions 41 | { 42 | public static Microsoft.AspNetCore.Builder.VoyagerEndpointConventionBuilder MapGraphQLVoyager(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern = "ui/voyager", GraphQL.Server.Ui.Voyager.VoyagerOptions? options = null) { } 43 | } 44 | } -------------------------------------------------------------------------------- /tests/ApiApprovalTests/netstandard20/GraphQL.Server.Ui.Altair.approved.txt: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Ui.Altair 2 | { 3 | public class AltairActionResult : Microsoft.AspNetCore.Mvc.IActionResult 4 | { 5 | public AltairActionResult(GraphQL.Server.Ui.Altair.AltairOptions options) { } 6 | public AltairActionResult(System.Action? configure = null) { } 7 | public System.Threading.Tasks.Task ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) { } 8 | } 9 | public class AltairMiddleware 10 | { 11 | public AltairMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.Server.Ui.Altair.AltairOptions options) { } 12 | public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext httpContext) { } 13 | } 14 | public class AltairOptions 15 | { 16 | public AltairOptions() { } 17 | public string GraphQLEndPoint { get; set; } 18 | public System.Collections.Generic.Dictionary? Headers { get; set; } 19 | public System.Func IndexStream { get; set; } 20 | public System.Func PostConfigure { get; set; } 21 | public System.Collections.Generic.Dictionary? Settings { get; set; } 22 | public string SubscriptionsEndPoint { get; set; } 23 | public System.Collections.Generic.Dictionary? SubscriptionsPayload { get; set; } 24 | } 25 | } 26 | namespace Microsoft.AspNetCore.Builder 27 | { 28 | public static class AltairApplicationBuilderExtensions 29 | { 30 | public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQLAltair(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, string path = "/ui/altair", GraphQL.Server.Ui.Altair.AltairOptions? options = null) { } 31 | } 32 | } -------------------------------------------------------------------------------- /tests/ApiApprovalTests/netstandard20/GraphQL.Server.Ui.GraphiQL.approved.txt: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Ui.GraphiQL 2 | { 3 | public class GraphiQLActionResult : Microsoft.AspNetCore.Mvc.IActionResult 4 | { 5 | public GraphiQLActionResult(GraphQL.Server.Ui.GraphiQL.GraphiQLOptions options) { } 6 | public GraphiQLActionResult(System.Action? configure = null) { } 7 | public System.Threading.Tasks.Task ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) { } 8 | } 9 | public class GraphiQLMiddleware 10 | { 11 | public GraphiQLMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.Server.Ui.GraphiQL.GraphiQLOptions options) { } 12 | public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext httpContext) { } 13 | } 14 | public class GraphiQLOptions 15 | { 16 | public GraphiQLOptions() { } 17 | [System.Obsolete("This property has no effect and will be removed in a future version.")] 18 | public bool ExplorerExtensionEnabled { get; set; } 19 | public string GraphQLEndPoint { get; set; } 20 | public bool GraphQLWsSubscriptions { get; set; } 21 | public bool HeaderEditorEnabled { get; set; } 22 | public System.Collections.Generic.Dictionary? Headers { get; set; } 23 | public System.Func IndexStream { get; set; } 24 | public System.Func PostConfigure { get; set; } 25 | public GraphQL.Server.Ui.GraphiQL.RequestCredentials RequestCredentials { get; set; } 26 | public string SubscriptionsEndPoint { get; set; } 27 | } 28 | public enum RequestCredentials 29 | { 30 | Omit = 0, 31 | Include = 1, 32 | SameOrigin = 2, 33 | } 34 | } 35 | namespace Microsoft.AspNetCore.Builder 36 | { 37 | public static class GraphiQLApplicationBuilderExtensions 38 | { 39 | public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQLGraphiQL(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, string path = "/ui/graphiql", GraphQL.Server.Ui.GraphiQL.GraphiQLOptions? options = null) { } 40 | } 41 | } -------------------------------------------------------------------------------- /tests/ApiApprovalTests/netstandard20/GraphQL.Server.Ui.Voyager.approved.txt: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Server.Ui.Voyager 2 | { 3 | public enum RequestCredentials 4 | { 5 | Omit = 0, 6 | Include = 1, 7 | SameOrigin = 2, 8 | } 9 | public class VoyagerActionResult : Microsoft.AspNetCore.Mvc.IActionResult 10 | { 11 | public VoyagerActionResult(GraphQL.Server.Ui.Voyager.VoyagerOptions options) { } 12 | public VoyagerActionResult(System.Action? configure = null) { } 13 | public System.Threading.Tasks.Task ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) { } 14 | } 15 | public class VoyagerMiddleware 16 | { 17 | public VoyagerMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.Server.Ui.Voyager.VoyagerOptions options) { } 18 | public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext httpContext) { } 19 | } 20 | public class VoyagerOptions 21 | { 22 | public VoyagerOptions() { } 23 | public string GraphQLEndPoint { get; set; } 24 | public System.Collections.Generic.Dictionary? Headers { get; set; } 25 | public System.Func IndexStream { get; set; } 26 | public System.Func PostConfigure { get; set; } 27 | public GraphQL.Server.Ui.Voyager.RequestCredentials RequestCredentials { get; set; } 28 | } 29 | } 30 | namespace Microsoft.AspNetCore.Builder 31 | { 32 | public static class VoyagerApplicationBuilderExtensions 33 | { 34 | public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQLVoyager(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, string path = "/ui/voyager", GraphQL.Server.Ui.Voyager.VoyagerOptions? options = null) { } 35 | } 36 | } -------------------------------------------------------------------------------- /tests/Samples.Authorization.Tests/EndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Samples.Tests; 3 | 4 | namespace Samples.Authorization.Tests; 5 | 6 | public class EndToEndTests 7 | { 8 | private const string SUCCESS_QUERY = "{hello}"; 9 | private const string SUCCESS_RESPONSE = """{"data":{"hello":"Hello anybody."}}"""; 10 | private const string ACCESS_DENIED_QUERY = "{helloUser}"; 11 | private const string ACCESS_DENIED_ERRORS = """[{"message":"Access denied for field \u0027helloUser\u0027 on type \u0027Query\u0027.","locations":[{"line":1,"column":2}],"extensions":{"code":"ACCESS_DENIED","codes":["ACCESS_DENIED"]}}]"""; 12 | private const string ACCESS_DENIED_RESPONSE = @"{""errors"":" + ACCESS_DENIED_ERRORS + "}"; 13 | 14 | [Fact] 15 | public Task GraphiQL() 16 | => new ServerTests().VerifyGraphiQLAsync("/ui/graphql"); 17 | 18 | [Fact] 19 | public Task GraphQLGet_Success() 20 | => new ServerTests().VerifyGraphQLGetAsync("/graphql", SUCCESS_QUERY, SUCCESS_RESPONSE); 21 | 22 | [Fact] 23 | public Task GraphQLGet_AccessDenied() 24 | => new ServerTests().VerifyGraphQLGetAsync("/graphql", ACCESS_DENIED_QUERY, ACCESS_DENIED_RESPONSE, HttpStatusCode.Unauthorized); 25 | 26 | [Fact] 27 | public Task GraphQLPost_Success() 28 | => new ServerTests().VerifyGraphQLPostAsync("/graphql", SUCCESS_QUERY, SUCCESS_RESPONSE); 29 | 30 | [Fact] 31 | public Task GraphQPost_AccessDenied() 32 | => new ServerTests().VerifyGraphQLPostAsync("/graphql", ACCESS_DENIED_QUERY, ACCESS_DENIED_RESPONSE, HttpStatusCode.Unauthorized); 33 | 34 | [Fact] 35 | public Task GraphQLWebSocket_Success() 36 | => new ServerTests().VerifyGraphQLWebSocketsAsync("/graphql", SUCCESS_QUERY, SUCCESS_RESPONSE); 37 | 38 | [Fact] 39 | public Task GraphQLWebSocket_AccessDenied() 40 | => new ServerTests().VerifyGraphQLWebSocketsAsync("/graphql", ACCESS_DENIED_QUERY, ACCESS_DENIED_ERRORS, false); 41 | } 42 | -------------------------------------------------------------------------------- /tests/Samples.Authorization.Tests/Samples.Authorization.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | End to end tests for the Samples.Authorization project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Samples.AzureFunctions.Tests/Samples.AzureFunctions.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net6.0 6 | End to end tests for the Samples.AzureFunctions project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/Samples.Basic.Tests/EndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using Samples.Tests; 2 | 3 | namespace Samples.Basic.Tests; 4 | 5 | public class EndToEndTests 6 | { 7 | [Fact] 8 | public Task GraphiQL() 9 | => new ServerTests().VerifyGraphiQLAsync(); 10 | 11 | [Fact] 12 | public Task GraphQLGet() 13 | => new ServerTests().VerifyGraphQLGetAsync(); 14 | 15 | [Fact] 16 | public Task GraphQLPost() 17 | => new ServerTests().VerifyGraphQLPostAsync(); 18 | 19 | [Fact] 20 | public Task GraphQLWebSocket() 21 | => new ServerTests().VerifyGraphQLWebSocketsAsync(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/Samples.Basic.Tests/Samples.Basic.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | End to end tests for the Samples.Basic project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Samples.Complex.Tests/DITest.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using GraphQL.Samples.Complex; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Samples.Server.Tests; 8 | 9 | public class DITest 10 | { 11 | [Fact] 12 | public void Services_Should_Contain_Only_One_DocumentWriter() 13 | { 14 | var cfg = new ConfigurationBuilder().Build(); 15 | var env = (IWebHostEnvironment)Activator.CreateInstance(Type.GetType("Microsoft.AspNetCore.Hosting.HostingEnvironment, Microsoft.AspNetCore.Hosting")!)!; 16 | var startup = new Startup(cfg, env); 17 | var services = new ServiceCollection(); 18 | startup.ConfigureServices(services); 19 | var provider = services.BuildServiceProvider(); 20 | var serializers = provider.GetServices(); 21 | serializers.Count().ShouldBe(1); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Samples.Complex.Tests/RequestType.cs: -------------------------------------------------------------------------------- 1 | namespace Samples.Server.Tests; 2 | 3 | /// 4 | /// Different types of HTTP requests a GraphQL HTTP server should be able to understand. 5 | /// See: https://graphql.org/learn/serving-over-http/ 6 | /// 7 | public enum RequestType 8 | { 9 | Get, 10 | PostWithJson, 11 | PostWithForm, 12 | PostWithGraph, 13 | } 14 | -------------------------------------------------------------------------------- /tests/Samples.Complex.Tests/Samples.Complex.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0;net6.0 6 | End to end tests for Samples.Server project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Samples.Complex.Tests/ShouldBeExtensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace Samples.Server.Tests; 4 | 5 | internal static class ShouldBeExtensions 6 | { 7 | /// 8 | /// Compares two strings after normalizing any encoding differences first. 9 | /// 10 | /// Actual. 11 | /// Expected. 12 | /// 13 | /// Pass true to ignore the `extensions` path of the JSON when comparing for equality. 14 | /// 15 | public static void ShouldBeEquivalentJson(this string actual, string expected, bool ignoreExtensions) 16 | { 17 | if (ignoreExtensions) 18 | { 19 | if (actual.StartsWith('[')) 20 | { 21 | var json = JArray.Parse(actual); 22 | foreach (var item in json) 23 | { 24 | if (item is JObject obj) 25 | { 26 | obj.Remove("extensions"); 27 | } 28 | } 29 | actual = json.ToString(Newtonsoft.Json.Formatting.None); 30 | } 31 | else 32 | { 33 | var json = JObject.Parse(actual); 34 | json.Remove("extensions"); 35 | actual = json.ToString(Newtonsoft.Json.Formatting.None); 36 | } 37 | } 38 | 39 | actual.ShouldBeEquivalentJson(expected); 40 | } 41 | 42 | /// 43 | /// Compares two strings after normalizing any encoding differences first. 44 | /// 45 | /// Actual. 46 | /// Expected. 47 | public static void ShouldBeEquivalentJson(this string actualJson, string expectedJson) 48 | { 49 | expectedJson = expectedJson.NormalizeJson(); 50 | actualJson = actualJson.NormalizeJson(); 51 | 52 | actualJson.ShouldBe(expectedJson); 53 | } 54 | 55 | private static string NormalizeJson(this string json) => json.Replace("'", @"\u0027").Replace("\"", @"\u0022"); 56 | } 57 | -------------------------------------------------------------------------------- /tests/Samples.Complex.Tests/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | 3 | namespace Samples.Server.Tests; 4 | 5 | internal static class StringExtensions 6 | { 7 | public static Inputs? ToInputs(this string? value) 8 | => new GraphQL.SystemTextJson.GraphQLSerializer().Deserialize(value); 9 | } 10 | -------------------------------------------------------------------------------- /tests/Samples.Controller.Tests/EndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using Samples.Tests; 2 | 3 | namespace Samples.Controller.Tests; 4 | 5 | public class EndToEndTests 6 | { 7 | [Fact] 8 | public Task GraphiQL() 9 | => new ServerTests().VerifyGraphiQLAsync(); 10 | 11 | [Fact] 12 | public Task GraphQLGet() 13 | => new ServerTests().VerifyGraphQLGetAsync("/home/graphql"); 14 | 15 | [Fact] 16 | public Task GraphQLPost() 17 | => new ServerTests().VerifyGraphQLPostAsync("/home/graphql"); 18 | 19 | [Fact] 20 | public Task GraphQLWebSocket() 21 | => new ServerTests().VerifyGraphQLWebSocketsAsync("/home/graphql"); 22 | 23 | [Fact] 24 | public Task GraphQL2Get() 25 | => new ServerTests().VerifyGraphQLGetAsync("/home/graphql2"); 26 | 27 | [Fact] 28 | public Task GraphQL2Post() 29 | => new ServerTests().VerifyGraphQLPostAsync("/home/graphql2"); 30 | } 31 | -------------------------------------------------------------------------------- /tests/Samples.Controller.Tests/Samples.Controller.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | End to end tests for the Samples.Controller project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Samples.Cors.Tests/EndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using Samples.Tests; 2 | 3 | namespace Samples.Cors.Tests; 4 | 5 | public class EndToEndTests 6 | { 7 | [Fact] 8 | public Task GraphiQL() 9 | => new ServerTests().VerifyGraphiQLAsync(); 10 | 11 | [Fact] 12 | public Task GraphQLGet() 13 | => new ServerTests().VerifyGraphQLGetAsync(); 14 | 15 | [Fact] 16 | public Task GraphQLPost() 17 | => new ServerTests().VerifyGraphQLPostAsync(); 18 | 19 | [Fact] 20 | public Task GraphQLWebSocket() 21 | => new ServerTests().VerifyGraphQLWebSocketsAsync(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/Samples.Cors.Tests/Samples.Cors.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | End to end tests for the Samples.Cors project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Samples.EndpointRouting.Tests/EndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using Samples.Tests; 2 | 3 | namespace Samples.EndpointRouting.Tests; 4 | 5 | public class EndToEndTests 6 | { 7 | [Fact] 8 | public Task GraphiQL() 9 | => new ServerTests().VerifyGraphiQLAsync(); 10 | 11 | [Fact] 12 | public Task GraphQLGet() 13 | => new ServerTests().VerifyGraphQLGetAsync(); 14 | 15 | [Fact] 16 | public Task GraphQLPost() 17 | => new ServerTests().VerifyGraphQLPostAsync(); 18 | 19 | [Fact] 20 | public Task GraphQLWebSocket() 21 | => new ServerTests().VerifyGraphQLWebSocketsAsync(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/Samples.EndpointRouting.Tests/Samples.EndpointRouting.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | End to end tests for the Samples.EndpointRouting project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Samples.Jwt.Tests/Samples.Jwt.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | End to end tests for the Samples.Jwt project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Samples.MultipleSchemas.Tests/EndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using Samples.Tests; 2 | 3 | namespace Samples.MultipleSchemas.Tests; 4 | 5 | public class EndToEndTests 6 | { 7 | private const string CAT_QUERY = """{ cat(id:"1") { name } }"""; 8 | private const string CAT_RESPONSE = """{"data":{"cat":{"name":"Fluffy"}}}"""; 9 | private const string DOG_QUERY = """{ dog(id:"1") { name } }"""; 10 | private const string DOG_RESPONSE = """{"data":{"dog":{"name":"Shadow"}}}"""; 11 | 12 | [Fact] 13 | public Task Cats_GraphiQL() 14 | => new ServerTests().VerifyGraphiQLAsync("/cats/ui/graphiql"); 15 | 16 | [Fact] 17 | public Task Dogs_GraphiQL() 18 | => new ServerTests().VerifyGraphiQLAsync("/dogs/ui/graphiql"); 19 | 20 | [Fact] 21 | public Task Cats_GraphQLGet() 22 | => new ServerTests().VerifyGraphQLGetAsync("/cats/graphql", CAT_QUERY, CAT_RESPONSE); 23 | 24 | [Fact] 25 | public Task Cats_GraphQLPost() 26 | => new ServerTests().VerifyGraphQLPostAsync("/cats/graphql", CAT_QUERY, CAT_RESPONSE); 27 | 28 | [Fact] 29 | public Task Cats_GraphQLWebSockets() 30 | => new ServerTests().VerifyGraphQLWebSocketsAsync("/cats/graphql", CAT_QUERY, CAT_RESPONSE); 31 | 32 | [Fact] 33 | public Task Dogs_GraphQLGet() 34 | => new ServerTests().VerifyGraphQLGetAsync("/dogs/graphql", DOG_QUERY, DOG_RESPONSE); 35 | 36 | [Fact] 37 | public Task Dogs_GraphQLPost() 38 | => new ServerTests().VerifyGraphQLPostAsync("/dogs/graphql", DOG_QUERY, DOG_RESPONSE); 39 | 40 | [Fact] 41 | public Task Dogs_GraphQLWebSockets() 42 | => new ServerTests().VerifyGraphQLWebSocketsAsync("/dogs/graphql", DOG_QUERY, DOG_RESPONSE); 43 | } 44 | -------------------------------------------------------------------------------- /tests/Samples.MultipleSchemas.Tests/Samples.MultipleSchemas.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | End to end tests for the Samples.MultipleSchemas project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Samples.Net48.Tests/EndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Server.Samples.Net48; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.TestHost; 4 | using Samples.Tests; 5 | 6 | namespace Samples.Net48.Tests; 7 | 8 | public class EndToEndTests : IDisposable 9 | { 10 | private readonly TestServer _testServer; 11 | 12 | public EndToEndTests() 13 | { 14 | var builder = new WebHostBuilder() 15 | .UseEnvironment("Development") 16 | .UseStartup(); 17 | 18 | _testServer = new TestServer(builder); 19 | } 20 | 21 | public void Dispose() => _testServer.Dispose(); 22 | 23 | [Fact] 24 | public async Task RootRedirect() 25 | { 26 | using var client = _testServer.CreateClient(); 27 | using var response = await client.GetAsync("/"); 28 | response.StatusCode.ShouldBe(System.Net.HttpStatusCode.Found); 29 | response.Headers.TryGetValues("Location", out var values).ShouldBeTrue(); 30 | values.ShouldHaveSingleItem().ShouldBe("/ui/graphql"); 31 | } 32 | 33 | [Fact] 34 | public Task GraphiQL() 35 | => _testServer.VerifyGraphiQLAsync("/ui/graphql"); 36 | 37 | [Fact] 38 | public Task GraphQLGet() 39 | => _testServer.VerifyGraphQLGetAsync(); 40 | 41 | [Fact] 42 | public Task GraphQLPost() 43 | => _testServer.VerifyGraphQLPostAsync(); 44 | 45 | [Fact] 46 | public Task GraphQLWebSockets() 47 | => _testServer.VerifyGraphQLWebSocketsAsync(); 48 | } 49 | -------------------------------------------------------------------------------- /tests/Samples.Net48.Tests/Samples.Net48.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | true 7 | 8 | 9 | net48;netcoreapp2.1 10 | true 11 | 12 | 13 | netcoreapp2.1 14 | true 15 | 16 | 17 | End to end tests for the Samples.Net48 project 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | false 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/Samples.Pages.Tests/EndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using Samples.Tests; 2 | 3 | namespace Samples.Pages.Tests; 4 | 5 | public class EndToEndTests 6 | { 7 | [Fact] 8 | public Task GraphiQL() 9 | => new ServerTests().VerifyGraphiQLAsync(); 10 | 11 | [Fact] 12 | public Task GraphQLGet() 13 | => new ServerTests().VerifyGraphQLGetAsync(); 14 | 15 | [Fact] 16 | public Task GraphQLPost() 17 | => new ServerTests().VerifyGraphQLPostAsync(); 18 | 19 | [Fact] 20 | public Task GraphQLWebSocket() 21 | => new ServerTests().VerifyGraphQLWebSocketsAsync(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/Samples.Pages.Tests/Samples.Pages.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | End to end tests for the Samples.Pages project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Samples.Tests/Samples.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0;net6.0 6 | true 7 | 8 | 9 | net8.0;net6.0;netcoreapp2.1;net48 10 | true 11 | 12 | 13 | End to end tests for sample projects 14 | false 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/Samples.Tests/ServerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.AspNetCore.Mvc.Testing; 3 | 4 | namespace Samples.Tests; 5 | 6 | public class ServerTests where TProgram : class 7 | { 8 | public async Task VerifyPlaygroundAsync(string url = "/") 9 | { 10 | using var webApp = new WebApplicationFactory(); 11 | await webApp.Server.VerifyPlaygroundAsync(url); 12 | } 13 | 14 | public async Task VerifyGraphiQLAsync(string url = "/") 15 | { 16 | using var webApp = new WebApplicationFactory(); 17 | await webApp.Server.VerifyGraphiQLAsync(url); 18 | } 19 | 20 | public async Task VerifyGraphQLGetAsync(string url = "/graphql", string query = "{count}", string expected = """{"data":{"count":0}}""", HttpStatusCode statusCode = HttpStatusCode.OK, string? jwtToken = null) 21 | { 22 | using var webApp = new WebApplicationFactory(); 23 | await webApp.Server.VerifyGraphQLGetAsync(url, query, expected, statusCode, jwtToken); 24 | } 25 | 26 | public async Task VerifyGraphQLPostAsync(string url = "/graphql", string query = "{count}", string expected = """{"data":{"count":0}}""", HttpStatusCode statusCode = HttpStatusCode.OK, string? jwtToken = null) 27 | { 28 | using var webApp = new WebApplicationFactory(); 29 | await webApp.Server.VerifyGraphQLPostAsync(url, query, expected, statusCode, jwtToken); 30 | } 31 | 32 | public async Task VerifyGraphQLWebSocketsAsync(string url = "/graphql", string query = "{count}", string expected = """{"data":{"count":0}}""", bool success = true, string? jwtToken = null) 33 | { 34 | using var webApp = new WebApplicationFactory(); 35 | await webApp.Server.VerifyGraphQLWebSocketsAsync(url, query, expected, success, jwtToken); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Samples.Tests/WebSocketExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.WebSockets; 2 | using System.Text; 3 | using System.Text.Json; 4 | using GraphQL.SystemTextJson; 5 | using GraphQL.Transport; 6 | #if NET48 7 | using MemoryBytes = System.ArraySegment; 8 | using ValueWebSocketReceiveResult = System.Net.WebSockets.WebSocketReceiveResult; 9 | #else 10 | using MemoryBytes = System.Memory; 11 | #endif 12 | 13 | namespace Samples.Tests; 14 | 15 | public static class WebSocketExtensions 16 | { 17 | private static readonly GraphQLSerializer _serializer = new(); 18 | 19 | public static Task SendMessageAsync(this WebSocket socket, OperationMessage message) 20 | => SendStringAsync(socket, _serializer.Serialize(message)); 21 | 22 | public static async Task SendStringAsync(this WebSocket socket, string str) 23 | { 24 | var bytes = Encoding.UTF8.GetBytes(str); 25 | await socket.SendAsync(new MemoryBytes(bytes), WebSocketMessageType.Text, true, default); 26 | } 27 | 28 | public static async Task ReceiveMessageAsync(this WebSocket socket) 29 | { 30 | using var cts = new CancellationTokenSource(); 31 | cts.CancelAfter(5000); 32 | var mem = new MemoryStream(); 33 | ValueWebSocketReceiveResult response; 34 | do 35 | { 36 | var buffer = new byte[1024]; 37 | response = await socket.ReceiveAsync(new MemoryBytes(buffer), cts.Token); 38 | mem.Write(buffer, 0, response.Count); 39 | } while (!response.EndOfMessage); 40 | response.MessageType.ShouldBe(WebSocketMessageType.Text); 41 | mem.Position = 0; 42 | var message = await _serializer.ReadAsync(mem); 43 | if (message!.Payload != null) 44 | message.Payload = ((JsonElement)message.Payload).GetRawText(); 45 | return message; 46 | } 47 | 48 | public static async Task ReceiveCloseMessageAsync(this WebSocket socket) 49 | { 50 | using var cts = new CancellationTokenSource(5000); 51 | var drainBuffer = new byte[1024]; 52 | ValueWebSocketReceiveResult response; 53 | do 54 | { 55 | response = await socket.ReceiveAsync(new MemoryBytes(drainBuffer), cts.Token); 56 | } while (!response.EndOfMessage); 57 | response.MessageType.ShouldBe(WebSocketMessageType.Close); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Samples.Upload.Tests/Samples.Upload.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0 6 | End to end tests for the Samples.Upload project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/Transports.AspNetCore.Tests/ShouldlyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace Tests; 4 | 5 | internal static class ShouldlyExtensions 6 | { 7 | public static Task ShouldBeAsync(this HttpResponseMessage message, bool badRequest, string expectedResponse) 8 | => ShouldBeAsync(message, badRequest ? HttpStatusCode.BadRequest : HttpStatusCode.OK, expectedResponse); 9 | 10 | public static Task ShouldBeAsync(this HttpResponseMessage message, string expectedResponse) 11 | => ShouldBeAsync(message, HttpStatusCode.OK, expectedResponse); 12 | 13 | public static Task ShouldBeAsync(this HttpResponseMessage message, HttpStatusCode httpStatusCode, string expectedResponse) 14 | => ShouldBeAsync(message, "application/graphql-response+json; charset=utf-8", httpStatusCode, expectedResponse); 15 | 16 | public static async Task ShouldBeAsync(this HttpResponseMessage message, string contentType, HttpStatusCode httpStatusCode, string expectedResponse) 17 | { 18 | message.StatusCode.ShouldBe(httpStatusCode); 19 | (message.Content.Headers.ContentType?.ToString()).ShouldBe(contentType); 20 | var actualResponse = await message.Content.ReadAsStringAsync(); 21 | actualResponse.ShouldBe(expectedResponse); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Transports.AspNetCore.Tests/WebSocketExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.WebSockets; 2 | using System.Text.Json; 3 | #if NET48 4 | using MemoryBytes = System.ArraySegment; 5 | using ValueWebSocketReceiveResult = System.Net.WebSockets.WebSocketReceiveResult; 6 | #else 7 | using MemoryBytes = System.Memory; 8 | #endif 9 | 10 | namespace Tests; 11 | 12 | public static class WebSocketExtensions 13 | { 14 | private static readonly GraphQLSerializer _serializer = new GraphQLSerializer(); 15 | 16 | public static Task SendMessageAsync(this WebSocket socket, OperationMessage message) 17 | => SendStringAsync(socket, _serializer.Serialize(message)); 18 | 19 | public static async Task SendStringAsync(this WebSocket socket, string str) 20 | { 21 | var bytes = Encoding.UTF8.GetBytes(str); 22 | await socket.SendAsync(new MemoryBytes(bytes), WebSocketMessageType.Text, true, default); 23 | } 24 | 25 | public static async Task ReceiveMessageAsync(this WebSocket socket) 26 | { 27 | using var cts = new CancellationTokenSource(); 28 | cts.CancelAfter(5000); 29 | var mem = new MemoryStream(); 30 | ValueWebSocketReceiveResult response; 31 | do 32 | { 33 | var buffer = new byte[1024]; 34 | response = await socket.ReceiveAsync(new MemoryBytes(buffer), cts.Token); 35 | mem.Write(buffer, 0, response.Count); 36 | } while (!response.EndOfMessage); 37 | response.MessageType.ShouldBe(WebSocketMessageType.Text); 38 | mem.Position = 0; 39 | var message = await _serializer.ReadAsync(mem); 40 | if (message!.Payload != null) 41 | message.Payload = ((JsonElement)message.Payload).GetRawText(); 42 | return message; 43 | } 44 | 45 | public static async Task ReceiveCloseAsync(this WebSocket socket) 46 | { 47 | using var cts = new CancellationTokenSource(); 48 | cts.CancelAfter(5000); 49 | var mem = new MemoryStream(); 50 | ValueWebSocketReceiveResult response; 51 | do 52 | { 53 | var buffer = new byte[1024]; 54 | response = await socket.ReceiveAsync(new MemoryBytes(buffer), cts.Token); 55 | mem.Write(buffer, 0, response.Count); 56 | } while (!response.EndOfMessage); 57 | response.MessageType.ShouldBe(WebSocketMessageType.Close); 58 | return socket.CloseStatus!.Value; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Transports.AspNetCore.Tests/WebSockets/TestOldSubscriptionServer.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Server.Transports.AspNetCore.WebSockets.SubscriptionsTransportWs; 2 | 3 | namespace Tests.WebSockets; 4 | 5 | public class TestOldSubscriptionServer : SubscriptionServer 6 | { 7 | public TestOldSubscriptionServer(IWebSocketConnection sendStream, GraphQLHttpMiddlewareOptions options, 8 | IDocumentExecuter executer, IGraphQLSerializer serializer, IServiceScopeFactory serviceScopeFactory, 9 | IUserContextBuilder userContextBuilder) 10 | : base(sendStream, options.WebSockets, options, executer, serializer, serviceScopeFactory, userContextBuilder) { } 11 | 12 | public bool Do_TryInitialize() 13 | => TryInitialize(); 14 | 15 | public Task Do_OnSendKeepAliveAsync() 16 | => OnSendKeepAliveAsync(); 17 | 18 | public Task Do_OnConnectionAcknowledgeAsync(OperationMessage message) 19 | => OnConnectionAcknowledgeAsync(message); 20 | 21 | public Task Do_OnStart(OperationMessage message) 22 | => OnStartAsync(message); 23 | 24 | public Task Do_OnStop(OperationMessage message) 25 | => OnStopAsync(message); 26 | 27 | public Task Do_SendErrorResultAsync(string id, ExecutionResult result) 28 | => SendErrorResultAsync(id, result); 29 | 30 | public Task Do_SendDataAsync(string id, ExecutionResult result) 31 | => SendDataAsync(id, result); 32 | 33 | public Task Do_SendCompletedAsync(string id) 34 | => SendCompletedAsync(id); 35 | 36 | public Task Do_ExecuteRequestAsync(OperationMessage message) 37 | => ExecuteRequestAsync(message); 38 | 39 | public Task Do_ErrorAccessDeniedAsync() 40 | => ErrorAccessDeniedAsync(); 41 | 42 | public SubscriptionList Get_Subscriptions 43 | => Subscriptions; 44 | 45 | public IGraphQLSerializer Get_Serializer => Serializer; 46 | 47 | public IDictionary? Get_UserContext => UserContext; 48 | 49 | public void Set_UserContext(IDictionary? userContext) => UserContext = userContext; 50 | 51 | public IUserContextBuilder Get_UserContextBuilder => UserContextBuilder; 52 | 53 | public IDocumentExecuter Get_DocumentExecuter => DocumentExecuter; 54 | 55 | public IServiceScopeFactory Get_ServiceScopeFactory => ServiceScopeFactory; 56 | } 57 | -------------------------------------------------------------------------------- /tests/Transports.AspNetCore.Tests/WebSockets/TestWebSocketConnection.cs: -------------------------------------------------------------------------------- 1 | using System.Net.WebSockets; 2 | 3 | namespace Tests.WebSockets; 4 | 5 | public class TestWebSocketConnection : WebSocketConnection 6 | { 7 | public TestWebSocketConnection( 8 | HttpContext httpContext, 9 | WebSocket webSocket, 10 | IGraphQLSerializer serializer, 11 | GraphQLHttpMiddlewareOptions options, 12 | CancellationToken cancellationToken) 13 | : base(httpContext, webSocket, serializer, options.WebSockets, cancellationToken) 14 | { 15 | } 16 | 17 | public Task Do_OnDispatchMessageAsync(IOperationMessageProcessor operationMessageReceiveStream, OperationMessage message) 18 | => OnDispatchMessageAsync(operationMessageReceiveStream, message); 19 | 20 | public Task Do_OnSendMessageAsync(OperationMessage message) 21 | => OnSendMessageAsync(message); 22 | 23 | public Task Do_OnCloseOutputAsync(WebSocketCloseStatus closeStatus, string? closeDescription) 24 | => OnCloseOutputAsync(closeStatus, closeDescription); 25 | 26 | public TimeSpan Get_DefaultDisconnectionTimeout 27 | => DefaultDisconnectionTimeout; 28 | 29 | public int Get_SendQueueCount => base.SendQueueCount; 30 | } 31 | --------------------------------------------------------------------------------