diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d20aef6e..2b0cc07f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,15 +71,81 @@ jobs: with: files: .coverage/GraphQL.Server.Transports.AspNetCore.Tests/coverage.net7.0.opencover.xml,.coverage/GraphQL.Server.Transports.AspNetCore.Tests/coverage.netcoreapp2.1.opencover.xml,.coverage/GraphQL.Server.Samples.Server.Tests/coverage.net7.0.opencover.xml + nativeaot: + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + name: NativeAOT Sample on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + steps: + - name: Checkout source + uses: actions/checkout@v4 + - name: Setup .NET SDK for NativeAOT + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json + env: + NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish NativeAOT sample + working-directory: samples/Samples.NativeAot + run: dotnet publish -c Release -o published + - name: Start NativeAOT sample in background + working-directory: samples/Samples.NativeAot/published + shell: bash + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + ./GraphQL.Server.Samples.NativeAot.exe & + else + ./GraphQL.Server.Samples.NativeAot & + fi + - name: Wait for NativeAOT sample to spin up + shell: bash + run: | + # Disable exit-on-error to allow retries + set +e + for i in {1..60}; do + echo "Request $i to the GraphQL endpoint..." + response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/ || true) + if [ "$response" -eq 200 ]; then + echo "Received 200 response, NativeAOT sample is ready." + exit 0 + fi + echo "Did not receive a 200 response, sleeping for 0.5 second..." + sleep 0.5 + done + echo "NativeAOT sample did not spin up in time." + exit 1 + - name: Run GraphQL query against NativeAOT sample + working-directory: samples/Samples.NativeAot + shell: bash + run: | + # Run a simple GraphQL query. Adjust the request as needed for your sample. + curl -X POST -H "Content-Type: application/json" \ + -d @sample-request.json \ + http://localhost:5000/graphql > nativeaot_response.json + - name: Print query result + working-directory: samples/Samples.NativeAot + shell: bash + run: cat nativeaot_response.json + - name: Compare query result to expected response + working-directory: samples/Samples.NativeAot + shell: bash + run: | + jq . nativeaot_response.json > actual-response.json + jq . sample-response.json > expected-response.json + diff -b actual-response.json expected-response.json + buildcheck: needs: - test + - nativeaot runs-on: ubuntu-latest if: always() steps: - name: Pass build check - if: ${{ needs.test.result == 'success' }} + if: ${{ needs.test.result == 'success' && needs.nativeaot.result == 'success' }} run: exit 0 - name: Fail build check - if: ${{ needs.test.result != 'success' }} + if: ${{ needs.test.result != 'success' || needs.nativeaot.result != 'success' }} run: exit 1 diff --git a/Directory.Build.props b/Directory.Build.props index a3ef8a3c..00e0ef5d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -32,7 +32,7 @@ GraphQL.Server.$(MSBuildProjectName) GraphQL.Server.$(MSBuildProjectName) - [8.2.1,9.0.0) + [8.3.0,9.0.0) true <_FriendAssembliesPublicKey>PublicKey=0024000004800000940000000602000000240000525341310004000001000100352162dbf27be78fc45136884b8f324aa9f1dfc928c96c24704bf1df1a8779b2f26c760ed8321eca5b95ea6bd9bb60cd025b300f73bd1f4ae1ee6e281f85c527fa013ab5cb2c3fc7a1cbef7f9bf0c9014152e6a21f6e0ac6a371f8b45c6d7139c9119df9eeecf1cf59063545bb7c07437b1bc12be2c57d108d72d6c27176fbb8 diff --git a/GraphQL.Server.sln b/GraphQL.Server.sln index ad78bc67..4775b9a1 100644 --- a/GraphQL.Server.sln +++ b/GraphQL.Server.sln @@ -122,6 +122,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Upload", "samples\S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Upload.Tests", "tests\Samples.Upload.Tests\Samples.Upload.Tests.csproj", "{DE3059F4-B548-4091-BFC0-5879246A2DF9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.NativeAot", "samples\Samples.NativeAot\Samples.NativeAot.csproj", "{56042483-2E36-41DF-9DC4-71DC527A36E4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -264,6 +266,10 @@ Global {DE3059F4-B548-4091-BFC0-5879246A2DF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {DE3059F4-B548-4091-BFC0-5879246A2DF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {DE3059F4-B548-4091-BFC0-5879246A2DF9}.Release|Any CPU.Build.0 = Release|Any CPU + {56042483-2E36-41DF-9DC4-71DC527A36E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56042483-2E36-41DF-9DC4-71DC527A36E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56042483-2E36-41DF-9DC4-71DC527A36E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56042483-2E36-41DF-9DC4-71DC527A36E4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -304,6 +310,7 @@ Global {A204E359-05E8-4CEE-891C-4CCA6570FA52} = {BBD07745-C962-4D2D-B302-6DA1BCC2FF43} {33E2CDF5-F854-4F1A-80D5-DBF0BDF8EEA8} = {5C07AFA3-12F2-40EA-807D-7A1EEF29012B} {DE3059F4-B548-4091-BFC0-5879246A2DF9} = {BBD07745-C962-4D2D-B302-6DA1BCC2FF43} + {56042483-2E36-41DF-9DC4-71DC527A36E4} = {5C07AFA3-12F2-40EA-807D-7A1EEF29012B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3FC7FA59-E938-453C-8C4A-9D5635A9489A} diff --git a/README.md b/README.md index e8a27db5..1d88207c 100644 --- a/README.md +++ b/README.md @@ -1138,6 +1138,7 @@ typical ASP.NET Core scenarios. | EndpointRouting | .NET 8 Minimal | Demonstrates configuring GraphQL through endpoint routing | | Jwt | .NET 8 Minimal | Demonstrates authenticating GraphQL requests with a JWT bearer token over HTTP POST and WebSocket connections | | MultipleSchemas | .NET 8 Minimal | Demonstrates configuring multiple schemas within a single server | +| NativeAot | .NET 8 Slim | Demonstrates configuring GraphQL for Native AOT publishing | | Net48 | .NET Core 2.1 / .NET 4.8 | Demonstrates configuring GraphQL on .NET 4.8 / Core 2.1 | | Pages | .NET 8 Minimal | Demonstrates configuring GraphQL on top of a Razor Pages template | | Upload | .NET 8 Minimal | Demonstrates uploading files via the `multipart/form-data` content type | diff --git a/samples/Samples.NativeAot/GraphTypes/QueryType.cs b/samples/Samples.NativeAot/GraphTypes/QueryType.cs new file mode 100644 index 00000000..a9015107 --- /dev/null +++ b/samples/Samples.NativeAot/GraphTypes/QueryType.cs @@ -0,0 +1,12 @@ +using GraphQL.Types; + +namespace GraphQL.Server.Samples.NativeAot.GraphTypes; + +public class QueryType : ObjectGraphType +{ + public QueryType() + { + Field("hello") + .Resolve(context => "world"); + } +} diff --git a/samples/Samples.NativeAot/MySchema.cs b/samples/Samples.NativeAot/MySchema.cs new file mode 100644 index 00000000..03205ab2 --- /dev/null +++ b/samples/Samples.NativeAot/MySchema.cs @@ -0,0 +1,13 @@ +using GraphQL.Server.Samples.NativeAot.GraphTypes; +using GraphQL.Types; + +namespace GraphQL.Server.Samples.NativeAot; + +public class MySchema : Schema +{ + public MySchema(IServiceProvider services, QueryType queryType) + : base(services) + { + Query = queryType; + } +} diff --git a/samples/Samples.NativeAot/Program.cs b/samples/Samples.NativeAot/Program.cs new file mode 100644 index 00000000..566583b6 --- /dev/null +++ b/samples/Samples.NativeAot/Program.cs @@ -0,0 +1,18 @@ +using GraphQL; +using GraphQL.Server.Samples.NativeAot; +using GraphQL.Server.Samples.NativeAot.GraphTypes; + +var builder = WebApplication.CreateSlimBuilder(args); + +builder.Services.AddGraphQL(b => b + .AddSchema() + .AddSystemTextJson()); + +builder.Services.AddTransient(); + +var app = builder.Build(); + +app.UseGraphQLGraphiQL("/"); +app.UseGraphQL(); + +app.Run(); diff --git a/samples/Samples.NativeAot/Properties/launchSettings.json b/samples/Samples.NativeAot/Properties/launchSettings.json new file mode 100644 index 00000000..45a6cec2 --- /dev/null +++ b/samples/Samples.NativeAot/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "", + "applicationUrl": "http://localhost:5003", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/Samples.NativeAot/Samples.NativeAot.csproj b/samples/Samples.NativeAot/Samples.NativeAot.csproj new file mode 100644 index 00000000..9f7e7b40 --- /dev/null +++ b/samples/Samples.NativeAot/Samples.NativeAot.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + true + true + true + + + + + + + + diff --git a/samples/Samples.NativeAot/appsettings.Development.json b/samples/Samples.NativeAot/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/samples/Samples.NativeAot/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/Samples.NativeAot/appsettings.json b/samples/Samples.NativeAot/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/samples/Samples.NativeAot/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/Samples.NativeAot/sample-request.json b/samples/Samples.NativeAot/sample-request.json new file mode 100644 index 00000000..767e195f --- /dev/null +++ b/samples/Samples.NativeAot/sample-request.json @@ -0,0 +1,3 @@ +{ + "query": "{ hello }" +} diff --git a/samples/Samples.NativeAot/sample-response.json b/samples/Samples.NativeAot/sample-response.json new file mode 100644 index 00000000..4b3fa5b6 --- /dev/null +++ b/samples/Samples.NativeAot/sample-response.json @@ -0,0 +1,5 @@ +{ + "data": { + "hello": "world" + } +}