diff --git a/src/HotChocolate/AspNetCore/benchmarks/k6/performance-data.json b/src/HotChocolate/AspNetCore/benchmarks/k6/performance-data.json index 1077ab05c85..f5de22fcef0 100644 --- a/src/HotChocolate/AspNetCore/benchmarks/k6/performance-data.json +++ b/src/HotChocolate/AspNetCore/benchmarks/k6/performance-data.json @@ -1,20 +1,20 @@ { - "timestamp": "2025-10-23T16:02:23Z", + "timestamp": "2025-10-23T19:09:41Z", "tests": { "single-fetch": { "name": "Single Fetch (50 products, names only)", "response_time": { - "min": 1.447863, - "p50": 2.751327, - "max": 58.155978, - "avg": 3.0016228840274928, - "p90": 4.130812, - "p95": 4.938353, - "p99": 7.987915299999998 + "min": 1.311196, + "p50": 1.763142, + "max": 50.855832, + "avg": 1.968737330531039, + "p90": 2.5424552000000005, + "p95": 2.9549014, + "p99": 5.398412600000002 }, "throughput": { - "requests_per_second": 78.71047667816899, - "total_iterations": 7162 + "requests_per_second": 78.78513545438359, + "total_iterations": 7168 }, "reliability": { "error_rate": 0 @@ -23,17 +23,17 @@ "dataloader": { "name": "DataLoader (50 products with brands)", "response_time": { - "min": 2.924864, - "p50": 4.8340905, - "max": 24.782141, - "avg": 5.312051972269166, - "p90": 7.203395599999999, - "p95": 8.972791199999993, - "p99": 12.379031920000001 + "min": 2.581101, + "p50": 3.39728, + "max": 19.088613, + "avg": 3.776861670740273, + "p90": 5.147565999999999, + "p95": 6.316323699999998, + "p99": 8.866344979999997 }, "throughput": { - "requests_per_second": 78.41421668508127, - "total_iterations": 7135 + "requests_per_second": 78.61021441670597, + "total_iterations": 7150 }, "reliability": { "error_rate": 0 diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/PersistedOperationTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/PersistedOperationTests.cs index ec172116e76..e9c0cef2aa9 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/PersistedOperationTests.cs +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/PersistedOperationTests.cs @@ -73,8 +73,8 @@ public async Task HotChocolateStyle_Sha1Hash_Success() var server = CreateStarWarsServer( configureServices: s => s - .AddSha1DocumentHashProvider(HashFormat.Hex) .AddGraphQL("StarWars") + .AddSha1DocumentHashProvider(HashFormat.Hex) .ConfigureSchemaServices(c => c.AddSingleton(storage)) .UsePersistedOperationPipeline()); @@ -100,8 +100,8 @@ public async Task HotChocolateStyle_Sha256Hash_Success() var server = CreateStarWarsServer( configureServices: s => s - .AddSha256DocumentHashProvider(HashFormat.Hex) .AddGraphQL("StarWars") + .AddSha256DocumentHashProvider(HashFormat.Hex) .ConfigureSchemaServices(c => c.AddSingleton(storage)) .UsePersistedOperationPipeline()); @@ -127,8 +127,8 @@ public async Task HotChocolateStyle_Sha256Hash_Query_Empty_String_Success() var server = CreateStarWarsServer( configureServices: s => s - .AddSha256DocumentHashProvider(HashFormat.Hex) .AddGraphQL("StarWars") + .AddSha256DocumentHashProvider(HashFormat.Hex) .ConfigureSchemaServices(c => c.AddSingleton(storage)) .UsePersistedOperationPipeline()); @@ -211,8 +211,8 @@ public async Task ApolloStyle_Sha1Hash_Success() var server = CreateStarWarsServer( configureServices: s => s - .AddSha1DocumentHashProvider(HashFormat.Hex) .AddGraphQL("StarWars") + .AddSha1DocumentHashProvider(HashFormat.Hex) .ConfigureSchemaServices(c => c.AddSingleton(storage)) .UsePersistedOperationPipeline()); @@ -238,8 +238,8 @@ public async Task ApolloStyle_Sha256Hash_Success() var server = CreateStarWarsServer( configureServices: s => s - .AddSha256DocumentHashProvider(HashFormat.Hex) .AddGraphQL("StarWars") + .AddSha256DocumentHashProvider(HashFormat.Hex) .ConfigureSchemaServices(c => c.AddSingleton(storage)) .UsePersistedOperationPipeline()); diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs index ed92a3b731b..c19c1c38bef 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs @@ -146,14 +146,6 @@ internal static IServiceCollection TryAddRequestExecutorResolver( return services; } - internal static IServiceCollection TryAddDefaultDocumentHashProvider( - this IServiceCollection services) - { - services.TryAddSingleton( - _ => new MD5DocumentHashProvider(HashFormat.Hex)); - return services; - } - internal static IServiceCollection TryAddDefaultBatchDispatcher( this IServiceCollection services, BatchDispatcherOptions options) diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Hashing.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Hashing.cs new file mode 100644 index 00000000000..79eb57ef394 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Hashing.cs @@ -0,0 +1,42 @@ +using HotChocolate.Execution.Configuration; +using HotChocolate.Language; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection; + +public static partial class RequestExecutorBuilderExtensions +{ + public static IRequestExecutorBuilder AddMD5DocumentHashProvider( + this IRequestExecutorBuilder builder, + HashFormat format = HashFormat.Base64) + { + return builder.ConfigureSchemaServices(services => + { + services.RemoveAll(); + services.AddSingleton(new MD5DocumentHashProvider(format)); + }); + } + + public static IRequestExecutorBuilder AddSha1DocumentHashProvider( + this IRequestExecutorBuilder builder, + HashFormat format = HashFormat.Base64) + { + return builder.ConfigureSchemaServices(services => + { + services.RemoveAll(); + services.AddSingleton(new Sha1DocumentHashProvider(format)); + }); + } + + public static IRequestExecutorBuilder AddSha256DocumentHashProvider( + this IRequestExecutorBuilder builder, + HashFormat format = HashFormat.Base64) + { + return builder.ConfigureSchemaServices(services => + { + services.RemoveAll(); + services.AddSingleton(new Sha256DocumentHashProvider(format)); + }); + } +} diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs index 1594bd4a213..739b8c0df73 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs @@ -48,7 +48,6 @@ public static IServiceCollection AddGraphQLCore(this IServiceCollection services .TryAddTypeConverter() .TryAddInputFormatter() .TryAddInputParser() - .TryAddDefaultDocumentHashProvider() .TryAddDefaultBatchDispatcher(default) .TryAddDefaultDataLoaderRegistry() .TryAddDataLoaderParameterExpressionBuilder() @@ -168,36 +167,6 @@ private static DefaultRequestExecutorBuilder CreateBuilder( return builder; } - public static IServiceCollection AddMD5DocumentHashProvider( - this IServiceCollection services, - HashFormat format = HashFormat.Base64) - { - services.RemoveAll(); - services.AddSingleton( - new MD5DocumentHashProvider(format)); - return services; - } - - public static IServiceCollection AddSha1DocumentHashProvider( - this IServiceCollection services, - HashFormat format = HashFormat.Base64) - { - services.RemoveAll(); - services.AddSingleton( - new Sha1DocumentHashProvider(format)); - return services; - } - - public static IServiceCollection AddSha256DocumentHashProvider( - this IServiceCollection services, - HashFormat format = HashFormat.Base64) - { - services.RemoveAll(); - services.AddSingleton( - new Sha256DocumentHashProvider(format)); - return services; - } - public static IServiceCollection AddBatchDispatcher(this IServiceCollection services) where T : class, IBatchDispatcher { diff --git a/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj b/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj index 20b80b28ea4..a30275088fc 100644 --- a/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj +++ b/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj @@ -232,6 +232,9 @@ RequestExecutorBuilderExtensions.cs + + RequestExecutorBuilderExtensions.cs + diff --git a/src/HotChocolate/Core/src/Execution/RequestExecutorManager.cs b/src/HotChocolate/Core/src/Execution/RequestExecutorManager.cs index 73c84856eaf..2dac198ab62 100644 --- a/src/HotChocolate/Core/src/Execution/RequestExecutorManager.cs +++ b/src/HotChocolate/Core/src/Execution/RequestExecutorManager.cs @@ -288,8 +288,7 @@ await typeModuleChangeMonitor.ConfigureAsync(context, cancellationToken) serviceCollection.AddSingleton(); serviceCollection.AddSingleton( static sp => sp.GetRootServiceProvider().GetRequiredService()); - serviceCollection.AddSingleton( - static sp => sp.GetRootServiceProvider().GetRequiredService()); + serviceCollection.AddSingleton(static _ => new MD5DocumentHashProvider(HashFormat.Hex)); serviceCollection.TryAddDiagnosticEvents(); serviceCollection.TryAddOperationExecutors(); diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Hashing.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Hashing.cs new file mode 100644 index 00000000000..1c8685d5c38 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Hashing.cs @@ -0,0 +1,41 @@ +using HotChocolate.Fusion.Configuration; +using HotChocolate.Language; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection; + +public static partial class CoreFusionGatewayBuilderExtensions +{ + public static IFusionGatewayBuilder AddMD5DocumentHashProvider( + this IFusionGatewayBuilder builder, + HashFormat format = HashFormat.Base64) + { + return builder.ConfigureSchemaServices((_, services) => + { + services.RemoveAll(); + services.AddSingleton(new MD5DocumentHashProvider(format)); + }); + } + + public static IFusionGatewayBuilder AddSha1DocumentHashProvider( + this IFusionGatewayBuilder builder, + HashFormat format = HashFormat.Base64) + { + return builder.ConfigureSchemaServices((_, services) => + { + services.RemoveAll(); + services.AddSingleton(new Sha1DocumentHashProvider(format)); + }); + } + + public static IFusionGatewayBuilder AddSha256DocumentHashProvider( + this IFusionGatewayBuilder builder, + HashFormat format = HashFormat.Base64) + { + return builder.ConfigureSchemaServices((_, services) => + { + services.RemoveAll(); + services.AddSingleton(new Sha256DocumentHashProvider(format)); + }); + } +} diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/PersistedOperationTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/PersistedOperationTests.cs index 1cf6e8b7867..6f456fc722f 100644 --- a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/PersistedOperationTests.cs +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/PersistedOperationTests.cs @@ -70,92 +70,92 @@ public async Task HotChocolateStyle_MD5Hash_NotFound() result.HttpResponseMessage.MatchSnapshot(); } - // [Fact] - // public async Task HotChocolateStyle_Sha1Hash_Success() - // { - // // arrange - // var storage = new OperationStorage(); - // var hashProvider = new Sha1DocumentHashProvider(HashFormat.Hex); - // - // using var gateway = await CreateGatewayAsync(b => b - // .AddSha1DocumentHashProvider(HashFormat.Hex) - // .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) - // .UsePersistedOperationPipeline()); - // - // using var client = GraphQLHttpClient.Create(gateway.CreateClient()); - // - // const string query = "{ __typename }"; - // var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); - // storage.AddOperation(key.Value, query); - // - // var request = new OperationRequest(id: key.Value); - // - // // act - // using var result = await client.PostAsync( - // request, - // new Uri("http://localhost:5000/graphql")); - // - // // assert - // result.HttpResponseMessage.MatchSnapshot(); - // } - - // [Fact] - // public async Task HotChocolateStyle_Sha256Hash_Success() - // { - // // arrange - // var storage = new OperationStorage(); - // var hashProvider = new Sha256DocumentHashProvider(HashFormat.Hex); - // - // using var gateway = await CreateGatewayAsync(b => b - // .AddSha256DocumentHashProvider(HashFormat.Hex) - // .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) - // .UsePersistedOperationPipeline()); - // - // using var client = GraphQLHttpClient.Create(gateway.CreateClient()); - // - // const string query = "{ __typename }"; - // var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); - // storage.AddOperation(key.Value, query); - // - // var request = new OperationRequest(id: key.Value); - // - // // act - // using var result = await client.PostAsync( - // request, - // new Uri("http://localhost:5000/graphql")); - // - // // assert - // result.HttpResponseMessage.MatchSnapshot(); - // } - - // [Fact] - // public async Task HotChocolateStyle_Sha256Hash_Query_Empty_String_Success() - // { - // // arrange - // var storage = new OperationStorage(); - // var hashProvider = new Sha256DocumentHashProvider(HashFormat.Hex); - // - // using var gateway = await CreateGatewayAsync(b => b - // .AddSha256DocumentHashProvider(HashFormat.Hex) - // .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) - // .UsePersistedOperationPipeline()); - // - // using var client = GraphQLHttpClient.Create(gateway.CreateClient()); - // - // const string query = "{ __typename }"; - // var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); - // storage.AddOperation(key.Value, query); - // - // var request = new OperationRequest(query: string.Empty, id: key.Value); - // - // // act - // using var result = await client.PostAsync( - // request, - // new Uri("http://localhost:5000/graphql")); - // - // // assert - // result.HttpResponseMessage.MatchSnapshot(); - // } + [Fact] + public async Task HotChocolateStyle_Sha1Hash_Success() + { + // arrange + var storage = new OperationStorage(); + var hashProvider = new Sha1DocumentHashProvider(HashFormat.Hex); + + using var gateway = await CreateGatewayAsync(b => b + .AddSha1DocumentHashProvider(HashFormat.Hex) + .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) + .UsePersistedOperationPipeline()); + + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + const string query = "{ __typename }"; + var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); + storage.AddOperation(key.Value, query); + + var request = new OperationRequest(id: key.Value); + + // act + using var result = await client.PostAsync( + request, + new Uri("http://localhost:5000/graphql")); + + // assert + result.HttpResponseMessage.MatchSnapshot(); + } + + [Fact] + public async Task HotChocolateStyle_Sha256Hash_Success() + { + // arrange + var storage = new OperationStorage(); + var hashProvider = new Sha256DocumentHashProvider(HashFormat.Hex); + + using var gateway = await CreateGatewayAsync(b => b + .AddSha256DocumentHashProvider(HashFormat.Hex) + .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) + .UsePersistedOperationPipeline()); + + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + const string query = "{ __typename }"; + var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); + storage.AddOperation(key.Value, query); + + var request = new OperationRequest(id: key.Value); + + // act + using var result = await client.PostAsync( + request, + new Uri("http://localhost:5000/graphql")); + + // assert + result.HttpResponseMessage.MatchSnapshot(); + } + + [Fact] + public async Task HotChocolateStyle_Sha256Hash_Query_Empty_String_Success() + { + // arrange + var storage = new OperationStorage(); + var hashProvider = new Sha256DocumentHashProvider(HashFormat.Hex); + + using var gateway = await CreateGatewayAsync(b => b + .AddSha256DocumentHashProvider(HashFormat.Hex) + .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) + .UsePersistedOperationPipeline()); + + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + const string query = "{ __typename }"; + var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); + storage.AddOperation(key.Value, query); + + var request = new OperationRequest(query: string.Empty, id: key.Value); + + // act + using var result = await client.PostAsync( + request, + new Uri("http://localhost:5000/graphql")); + + // assert + result.HttpResponseMessage.MatchSnapshot(); + } [Fact] public async Task ApolloStyle_MD5Hash_Success() @@ -214,63 +214,63 @@ public async Task ApolloStyle_MD5Hash_NotFound() result.HttpResponseMessage.MatchSnapshot(); } - // [Fact] - // public async Task ApolloStyle_Sha1Hash_Success() - // { - // // arrange - // var storage = new OperationStorage(); - // var hashProvider = new Sha1DocumentHashProvider(HashFormat.Hex); - // - // using var gateway = await CreateGatewayAsync(b => b - // .AddSha1DocumentHashProvider(HashFormat.Hex) - // .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) - // .UsePersistedOperationPipeline()); - // - // using var client = GraphQLHttpClient.Create(gateway.CreateClient()); - // - // const string query = "{ __typename }"; - // var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); - // storage.AddOperation(key.Value, query); - // - // var request = CreateApolloStyleRequest(hashProvider.Name, key.Value); - // - // // act - // using var result = await client.PostAsync( - // request, - // new Uri("http://localhost:5000/graphql")); - // - // // assert - // result.HttpResponseMessage.MatchSnapshot(); - // } - - // [Fact] - // public async Task ApolloStyle_Sha256Hash_Success() - // { - // // arrange - // var storage = new OperationStorage(); - // var hashProvider = new Sha256DocumentHashProvider(HashFormat.Hex); - // - // using var gateway = await CreateGatewayAsync(b => b - // .AddSha256DocumentHashProvider(HashFormat.Hex) - // .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) - // .UsePersistedOperationPipeline()); - // - // using var client = GraphQLHttpClient.Create(gateway.CreateClient()); - // - // const string query = "{ __typename }"; - // var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); - // storage.AddOperation(key.Value, query); - // - // var request = CreateApolloStyleRequest(hashProvider.Name, key.Value); - // - // // act - // using var result = await client.PostAsync( - // request, - // new Uri("http://localhost:5000/graphql")); - // - // // assert - // result.HttpResponseMessage.MatchSnapshot(); - // } + [Fact] + public async Task ApolloStyle_Sha1Hash_Success() + { + // arrange + var storage = new OperationStorage(); + var hashProvider = new Sha1DocumentHashProvider(HashFormat.Hex); + + using var gateway = await CreateGatewayAsync(b => b + .AddSha1DocumentHashProvider(HashFormat.Hex) + .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) + .UsePersistedOperationPipeline()); + + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + const string query = "{ __typename }"; + var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); + storage.AddOperation(key.Value, query); + + var request = CreateApolloStyleRequest(hashProvider.Name, key.Value); + + // act + using var result = await client.PostAsync( + request, + new Uri("http://localhost:5000/graphql")); + + // assert + result.HttpResponseMessage.MatchSnapshot(); + } + + [Fact] + public async Task ApolloStyle_Sha256Hash_Success() + { + // arrange + var storage = new OperationStorage(); + var hashProvider = new Sha256DocumentHashProvider(HashFormat.Hex); + + using var gateway = await CreateGatewayAsync(b => b + .AddSha256DocumentHashProvider(HashFormat.Hex) + .ConfigureSchemaServices((_, sc) => sc.AddSingleton(storage)) + .UsePersistedOperationPipeline()); + + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + const string query = "{ __typename }"; + var key = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(query)); + storage.AddOperation(key.Value, query); + + var request = CreateApolloStyleRequest(hashProvider.Name, key.Value); + + // act + using var result = await client.PostAsync( + request, + new Uri("http://localhost:5000/graphql")); + + // assert + result.HttpResponseMessage.MatchSnapshot(); + } [Fact] public async Task Standard_Query_By_Default_Works() diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.ApolloStyle_Sha1Hash_Success.snap b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.ApolloStyle_Sha1Hash_Success.snap new file mode 100644 index 00000000000..14447b4c81a --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.ApolloStyle_Sha1Hash_Success.snap @@ -0,0 +1,6 @@ +Headers: +Content-Type: application/graphql-response+json; charset=utf-8 +--------------------------> +Status Code: OK +--------------------------> +{"data":{"__typename":"Query"}} diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.ApolloStyle_Sha256Hash_Success.snap b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.ApolloStyle_Sha256Hash_Success.snap new file mode 100644 index 00000000000..14447b4c81a --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.ApolloStyle_Sha256Hash_Success.snap @@ -0,0 +1,6 @@ +Headers: +Content-Type: application/graphql-response+json; charset=utf-8 +--------------------------> +Status Code: OK +--------------------------> +{"data":{"__typename":"Query"}} diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.HotChocolateStyle_Sha1Hash_Success.snap b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.HotChocolateStyle_Sha1Hash_Success.snap new file mode 100644 index 00000000000..14447b4c81a --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.HotChocolateStyle_Sha1Hash_Success.snap @@ -0,0 +1,6 @@ +Headers: +Content-Type: application/graphql-response+json; charset=utf-8 +--------------------------> +Status Code: OK +--------------------------> +{"data":{"__typename":"Query"}} diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.HotChocolateStyle_Sha256Hash_Query_Empty_String_Success.snap b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.HotChocolateStyle_Sha256Hash_Query_Empty_String_Success.snap new file mode 100644 index 00000000000..14447b4c81a --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.HotChocolateStyle_Sha256Hash_Query_Empty_String_Success.snap @@ -0,0 +1,6 @@ +Headers: +Content-Type: application/graphql-response+json; charset=utf-8 +--------------------------> +Status Code: OK +--------------------------> +{"data":{"__typename":"Query"}} diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.HotChocolateStyle_Sha256Hash_Success.snap b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.HotChocolateStyle_Sha256Hash_Success.snap new file mode 100644 index 00000000000..14447b4c81a --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/PersistedOperationTests.HotChocolateStyle_Sha256Hash_Success.snap @@ -0,0 +1,6 @@ +Headers: +Content-Type: application/graphql-response+json; charset=utf-8 +--------------------------> +Status Code: OK +--------------------------> +{"data":{"__typename":"Query"}} diff --git a/src/HotChocolate/Language/src/Language.Web/DocumentHashProviderBase.cs b/src/HotChocolate/Language/src/Language.Web/DocumentHashProviderBase.cs index 5337ee63483..1f24c7938f6 100644 --- a/src/HotChocolate/Language/src/Language.Web/DocumentHashProviderBase.cs +++ b/src/HotChocolate/Language/src/Language.Web/DocumentHashProviderBase.cs @@ -49,7 +49,11 @@ protected static string FormatHash(ReadOnlySpan hash, HashFormat format) => format switch { HashFormat.Base64 => ToBase64UrlSafeString(hash), +#if NET9_0_OR_GREATER + HashFormat.Hex => Convert.ToHexStringLower(hash), +#else HashFormat.Hex => ToHexString(hash), +#endif _ => throw new NotSupportedException(ComputeHash_FormatNotSupported) }; diff --git a/website/src/docs/hotchocolate/v16/migrating/migrate-from-15-to-16.md b/website/src/docs/hotchocolate/v16/migrating/migrate-from-15-to-16.md index b90d0ed7c54..f58170ebc84 100644 --- a/website/src/docs/hotchocolate/v16/migrating/migrate-from-15-to-16.md +++ b/website/src/docs/hotchocolate/v16/migrating/migrate-from-15-to-16.md @@ -40,8 +40,7 @@ builder.Services.AddGraphQLServer() ## Cache size configuration -Previously, configuring document and operation cache sizes required calling methods directly on `IServiceCollection` rather than using the standard `IRequestExecutorBuilder` pattern. We've now consolidated cache configuration with other GraphQL options for consistency. -If you're currently using `AddOperationCache` or `AddDocumentCache`, update your code as follows: +Previously, document and operation cache sizes were globally configured through the `IServiceCollection`. In an effort to align and properly scope our configuration APIs, we've moved the configuration of these caches to the `IRequestExecutorBuilder`. If you're currently calling `AddDocumentCache` or `AddOperationCache` directly on the `IServiceCollection`, move the configuration to `ModifyOptions` on the `IRequestExecutorBuilder`: ```diff -builder.Services.AddDocumentCache(200); @@ -55,6 +54,8 @@ builder.Services.AddGraphQLServer() + }); ``` +If your application contains multiple GraphQL servers, the cache configuration has to be repeated for each one as the configuration is now scoped to a particular GraphQL server. + If you were previously accessing `IDocumentCache` or `IPreparedOperationCache` through the root service provider, you now need to access it through the schema-specific service provider instead. For instance, to populate the document cache during startup, create a custom `IRequestExecutorWarmupTask` that injects `IDocumentCache`: @@ -76,6 +77,19 @@ public class MyWarmupTask(IDocumentCache cache) : IRequestExecutorWarmupTask } ``` +## Document hash provider configuration + +Previously, document hash providers were globally configured through the `IServiceCollection`. In an effort to align and properly scope our configuration APIs, we've moved the configuration of the hash provider to the `IRequestExecutorBuilder`. If you're currently calling `AddMD5DocumentHashProvider`, `AddSha256DocumentHashProvider` or `AddSha1DocumentHashProvider` directly on the `IServiceCollection`, move the call to the `IRequestExecutorBuilder`: + +```diff +-builder.Services.AddSha256DocumentHashProvider(); + +builder.Services.AddGraphQLServer() ++ .AddSha256DocumentHashProvider() +``` + +If your application contains multiple GraphQL servers, the hash provider configuration has to be repeated for each one as the configuration is now scoped to a particular GraphQL server. + ## MaxAllowedNodeBatchSize & EnsureAllNodesCanBeResolved options moved ```diff diff --git a/website/src/docs/hotchocolate/v16/performance/automatic-persisted-operations.md b/website/src/docs/hotchocolate/v16/performance/automatic-persisted-operations.md index 14c87c749a9..0636e4c3b5e 100644 --- a/website/src/docs/hotchocolate/v16/performance/automatic-persisted-operations.md +++ b/website/src/docs/hotchocolate/v16/performance/automatic-persisted-operations.md @@ -175,10 +175,10 @@ public void ConfigureServices(IServiceCollection services) services // Global Services .AddMemoryCache() - .AddSha256DocumentHashProvider(HashFormat.Hex) // GraphQL server configuration .AddGraphQLServer() + .AddSha256DocumentHashProvider(HashFormat.Hex) .AddQueryType() .UseAutomaticPersistedOperationPipeline() .AddInMemoryOperationDocumentStorage(); diff --git a/website/src/docs/hotchocolate/v16/performance/persisted-operations.md b/website/src/docs/hotchocolate/v16/performance/persisted-operations.md index 9e66065eb6a..3ee0b7940ee 100644 --- a/website/src/docs/hotchocolate/v16/performance/persisted-operations.md +++ b/website/src/docs/hotchocolate/v16/performance/persisted-operations.md @@ -148,12 +148,8 @@ Unlike with Redis, a Blob Storage client has no easy way to set the expiration o } }, "filters": { - "blobTypes": [ - "blockBlob" - ], - "prefixMatch": [ - "hotchocolate/" - ] + "blobTypes": ["blockBlob"], + "prefixMatch": ["hotchocolate/"] } } } @@ -169,14 +165,12 @@ Per default Hot Chocolate uses the MD5 hashing algorithm, but we can override th public void ConfigureServices(IServiceCollection services) { services + .AddGraphQLServer() + .AddQueryType() // choose one of the following providers .AddMD5DocumentHashProvider() .AddSha256DocumentHashProvider() .AddSha1DocumentHashProvider() - - // GraphQL server configuration - .AddGraphQLServer() - .AddQueryType() .UsePersistedOperationPipeline() .AddFileSystemOperationDocumentStorage("./persisted_operations"); } diff --git a/website/src/docs/hotchocolate/v16/security/index.md b/website/src/docs/hotchocolate/v16/security/index.md index b4db2d0dd55..f8f9136527c 100644 --- a/website/src/docs/hotchocolate/v16/security/index.md +++ b/website/src/docs/hotchocolate/v16/security/index.md @@ -98,7 +98,8 @@ Per default Hot Chocolate uses MD5 to create a unique document hash. Since MD5 i Fortunately, we offer the option to use the FIPS compliant SHA256 hashing algorithm to create document hashes. ```csharp -builder.Services.AddSha256DocumentHashProvider(); +builder.Services.AddGraphQLServer() + .AddSha256DocumentHashProvider(); ``` [Learn more about document hashing providers](/docs/hotchocolate/v16/performance/persisted-operations#hashing-algorithms)