diff --git a/.gitignore b/.gitignore index 1e9e96c1..7306e148 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ [Oo]bj/ [Bb]in/ + +*.received.txt diff --git a/README.md b/README.md index 0df80e66..2884b3f4 100644 --- a/README.md +++ b/README.md @@ -127,11 +127,14 @@ public void ConfigureServices(IServiceCollection services) // Add GraphQL services and configure options services .AddSingleton() - .AddSingleton() - .AddGraphQL((options, provider) => + .AddSingleton(); + + MicrosoftDI.GraphQLBuilderExtensions.AddGraphQL(services) + .AddServer(true) + .ConfigureExecution(options => { options.EnableMetrics = Environment.IsDevelopment(); - var logger = provider.GetRequiredService>(); + var logger = options.RequestServices.GetRequiredService>(); options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message); }) // Add required services for GraphQL request/response de/serialization @@ -140,7 +143,7 @@ public void ConfigureServices(IServiceCollection services) .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) .AddWebSockets() // Add required services for web socket support .AddDataLoader() // Add required services for DataLoader support - .AddGraphTypes(typeof(ChatSchema)) // Add all IGraphType implementors in assembly which ChatSchema exists + .AddGraphTypes(typeof(ChatSchema).Assembly) // Add all IGraphType implementors in assembly which ChatSchema exists } public void Configure(IApplicationBuilder app) @@ -177,11 +180,14 @@ public void ConfigureServices(IServiceCollection services) services .AddRouting() .AddSingleton() - .AddSingleton() - .AddGraphQL((options, provider) => + .AddSingleton(); + + MicrosoftDI.GraphQLBuilderExtensions.AddGraphQL(services) + .AddServer(true) + .ConfigureExecution(options => { options.EnableMetrics = Environment.IsDevelopment(); - var logger = provider.GetRequiredService>(); + var logger = options.RequestServices.GetRequiredService>(); options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message); }) // It is required when both GraphQL HTTP and GraphQL WebSockets middlewares are mapped to the same endpoint (by default 'graphql'). @@ -192,7 +198,7 @@ public void ConfigureServices(IServiceCollection services) .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) .AddWebSockets() // Add required services for web socket support .AddDataLoader() // Add required services for DataLoader support - .AddGraphTypes(typeof(ChatSchema)); // Add all IGraphType implementors in assembly which ChatSchema exists + .AddGraphTypes(typeof(ChatSchema).Assembly); // Add all IGraphType implementors in assembly which ChatSchema exists } public void Configure(IApplicationBuilder app) @@ -202,7 +208,7 @@ public void Configure(IApplicationBuilder app) // this is required for ASP.NET Core routing app.UseRouting(); - + app.UseEndpoints(endpoints => { // map websocket middleware for ChatSchema at default path /graphql diff --git a/samples/Samples.Server/Samples.Server.csproj b/samples/Samples.Server/Samples.Server.csproj index 62b16d5b..95c58ae7 100644 --- a/samples/Samples.Server/Samples.Server.csproj +++ b/samples/Samples.Server/Samples.Server.csproj @@ -11,6 +11,7 @@ + diff --git a/samples/Samples.Server/Startup.cs b/samples/Samples.Server/Startup.cs index 33e93bfe..e68f17dd 100644 --- a/samples/Samples.Server/Startup.cs +++ b/samples/Samples.Server/Startup.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using GraphQL.DataLoader; +using GraphQL.Execution; using GraphQL.Samples.Schemas.Chat; using GraphQL.Server; using GraphQL.Server.Ui.Altair; @@ -29,20 +31,23 @@ public Startup(IConfiguration configuration, IWebHostEnvironment environment) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services - .AddSingleton() - .AddSingleton() - .AddGraphQL((options, provider) => + services.AddSingleton(); + + MicrosoftDI.GraphQLBuilderExtensions.AddGraphQL(services) + .AddServer(true) + .AddSchema() + .ConfigureExecution(options => { options.EnableMetrics = Environment.IsDevelopment(); - var logger = provider.GetRequiredService>(); + var logger = options.RequestServices.GetRequiredService>(); options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message); }) - .AddSystemTextJson(deserializerSettings => { }, serializerSettings => { }) - .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) + .AddSystemTextJson() + .AddErrorInfoProvider() + .Configure(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) .AddWebSockets() .AddDataLoader() - .AddGraphTypes(typeof(ChatSchema)); + .AddGraphTypes(typeof(ChatSchema).Assembly); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/samples/Samples.Server/StartupWithRouting.cs b/samples/Samples.Server/StartupWithRouting.cs index 62efe64d..3b7c522e 100644 --- a/samples/Samples.Server/StartupWithRouting.cs +++ b/samples/Samples.Server/StartupWithRouting.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using GraphQL.DataLoader; +using GraphQL.Execution; using GraphQL.Samples.Schemas.Chat; using GraphQL.Server; using GraphQL.Server.Ui.Altair; @@ -29,22 +31,25 @@ public StartupWithRouting(IConfiguration configuration, IWebHostEnvironment envi // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services - .AddRouting() - .AddSingleton() - .AddSingleton() - .AddGraphQL((options, provider) => + services.AddRouting(); + services.AddSingleton(); + + MicrosoftDI.GraphQLBuilderExtensions.AddGraphQL(services) + .AddServer(true) + .AddSchema() + .ConfigureExecution(options => { options.EnableMetrics = Environment.IsDevelopment(); - var logger = provider.GetRequiredService>(); + var logger = options.RequestServices.GetRequiredService>(); options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message); }) .AddDefaultEndpointSelectorPolicy() - .AddSystemTextJson(deserializerSettings => { }, serializerSettings => { }) - .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) + .AddSystemTextJson() + .AddErrorInfoProvider() + .Configure(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) .AddWebSockets() .AddDataLoader() - .AddGraphTypes(typeof(ChatSchema)); + .AddGraphTypes(typeof(ChatSchema).Assembly); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/All/All.csproj b/src/All/All.csproj index e6164f3a..3040df3e 100644 --- a/src/All/All.csproj +++ b/src/All/All.csproj @@ -21,9 +21,9 @@ - - - + + + diff --git a/src/Authorization.AspNetCore/GraphQLBuilderAuthorizationExtensions.cs b/src/Authorization.AspNetCore/GraphQLBuilderAuthorizationExtensions.cs index aadece52..2d54b83b 100644 --- a/src/Authorization.AspNetCore/GraphQLBuilderAuthorizationExtensions.cs +++ b/src/Authorization.AspNetCore/GraphQLBuilderAuthorizationExtensions.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using GraphQL.Server.Authorization.AspNetCore; using GraphQL.Validation; @@ -14,6 +16,7 @@ public static class GraphQLBuilderAuthorizationExtensions /// /// The GraphQL builder. /// Reference to the passed . + [Obsolete] public static IGraphQLBuilder AddGraphQLAuthorization(this IGraphQLBuilder builder) => builder.AddGraphQLAuthorization(options => { }); @@ -23,6 +26,7 @@ public static IGraphQLBuilder AddGraphQLAuthorization(this IGraphQLBuilder build /// The GraphQL builder. /// An action delegate to configure the provided . /// Reference to the passed . + [Obsolete] public static IGraphQLBuilder AddGraphQLAuthorization(this IGraphQLBuilder builder, Action options) { builder.Services.TryAddTransient(); @@ -33,5 +37,34 @@ public static IGraphQLBuilder AddGraphQLAuthorization(this IGraphQLBuilder build return builder; } + + /// + /// Adds the GraphQL authorization. + /// + /// The GraphQL builder. + /// Reference to the passed . + public static DI.IGraphQLBuilder AddGraphQLAuthorization(this DI.IGraphQLBuilder builder) + => builder.AddGraphQLAuthorization(_ => { }); + + /// + /// Adds the GraphQL authorization. + /// + /// The GraphQL builder. + /// An action delegate to configure the provided . + /// Reference to the passed . + public static DI.IGraphQLBuilder AddGraphQLAuthorization(this DI.IGraphQLBuilder builder, Action? configure) + { + if (!(builder is IServiceCollection services)) + throw new NotSupportedException("This method only supports the MicrosoftDI implementation of IGraphQLBuilder."); + services.TryAddTransient(); + services.AddHttpContextAccessor(); + if (configure != null) + services.AddAuthorizationCore(configure); + else + services.AddAuthorizationCore(); + builder.AddValidationRule(); + + return builder; + } } } diff --git a/src/Core/BasicGraphQLExecuter.cs b/src/Core/BasicGraphQLExecuter.cs new file mode 100644 index 00000000..8f21ffcd --- /dev/null +++ b/src/Core/BasicGraphQLExecuter.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using GraphQL.Instrumentation; +using GraphQL.Types; +using Microsoft.Extensions.Options; + +namespace GraphQL.Server +{ + public class BasicGraphQLExecuter : IGraphQLExecuter + where TSchema : ISchema + { + public TSchema Schema { get; } + + private readonly IDocumentExecuter _documentExecuter; + private readonly GraphQLOptions _options; + + public BasicGraphQLExecuter( + TSchema schema, + IDocumentExecuter documentExecuter, + IOptions options) + { + Schema = schema; + + _documentExecuter = documentExecuter; + _options = options.Value; + } + + public virtual async Task ExecuteAsync(string operationName, string query, Inputs variables, IDictionary context, IServiceProvider requestServices, CancellationToken cancellationToken = default) + { + var start = DateTime.UtcNow; + + var options = GetOptions(operationName, query, variables, context, requestServices, cancellationToken); + var result = await _documentExecuter.ExecuteAsync(options); + + if (options.EnableMetrics) + { + result.EnrichWithApolloTracing(start); + } + + return result; + } + + protected virtual ExecutionOptions GetOptions(string operationName, string query, Inputs variables, IDictionary context, IServiceProvider requestServices, CancellationToken cancellationToken) + { + var opts = new ExecutionOptions + { + Schema = Schema, + OperationName = operationName, + Query = query, + Inputs = variables, + UserContext = context, + CancellationToken = cancellationToken, + ComplexityConfiguration = _options.ComplexityConfiguration, + EnableMetrics = _options.EnableMetrics, + UnhandledExceptionDelegate = _options.UnhandledExceptionDelegate, + MaxParallelExecutionCount = _options.MaxParallelExecutionCount, + RequestServices = requestServices, + }; + + return opts; + } + } +} diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 4c479e46..ecf9fc5f 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/src/Core/DefaultGraphQLExecuter.cs b/src/Core/DefaultGraphQLExecuter.cs index 5057b4ad..ceeb114a 100644 --- a/src/Core/DefaultGraphQLExecuter.cs +++ b/src/Core/DefaultGraphQLExecuter.cs @@ -11,6 +11,7 @@ namespace GraphQL.Server { + [Obsolete] public class DefaultGraphQLExecuter : IGraphQLExecuter where TSchema : ISchema { diff --git a/src/Core/Extensions/GraphQLBuilderCoreExtensions.cs b/src/Core/Extensions/GraphQLBuilderCoreExtensions.cs index 463abf2f..75c4b6d1 100644 --- a/src/Core/Extensions/GraphQLBuilderCoreExtensions.cs +++ b/src/Core/Extensions/GraphQLBuilderCoreExtensions.cs @@ -1,8 +1,11 @@ +#nullable enable + using System; using System.Linq; using System.Reflection; using GraphQL.DataLoader; using GraphQL.Execution; +using GraphQL.Instrumentation; using GraphQL.Types; using GraphQL.Types.Relay; using Microsoft.Extensions.DependencyInjection; @@ -16,12 +19,45 @@ namespace GraphQL.Server /// public static class GraphQLBuilderCoreExtensions { + /// + /// Registers an instance of as for + /// use with . It is recommended to use directly rather than + /// use the interface. + ///

+ /// Also installs in the schema if specified. (Previous' versions of + /// GraphQL.Server would install this middleware automatically.) Specify for + /// if will be called separately. Note that + /// the implementation of will set + /// if is set. + ///
+ public static DI.IGraphQLBuilder AddServer(this DI.IGraphQLBuilder builder, bool installMetricsMiddleware, Action? configureOptions) + { + builder.TryRegister(typeof(IGraphQLExecuter<>), typeof(BasicGraphQLExecuter<>), DI.ServiceLifetime.Transient); + builder.TryRegister(typeof(IGraphQLExecuter), typeof(BasicGraphQLExecuter), DI.ServiceLifetime.Transient); + builder.Configure(configureOptions); + if (installMetricsMiddleware) + builder.AddMetrics(false); + return builder; + } + + /// + public static DI.IGraphQLBuilder AddServer(this DI.IGraphQLBuilder builder, bool installMetricsMiddleware, Action? configureOptions = null) + { + builder.TryRegister(typeof(IGraphQLExecuter<>), typeof(BasicGraphQLExecuter<>), DI.ServiceLifetime.Transient); + builder.TryRegister(typeof(IGraphQLExecuter), typeof(BasicGraphQLExecuter), DI.ServiceLifetime.Transient); + builder.Configure(configureOptions); + if (installMetricsMiddleware) + builder.AddMetrics(false); + return builder; + } + /// /// Provides the ability to configure for . /// /// GraphQL builder used for GraphQL specific extension methods as 'this' argument. /// Action to configure the . /// Reference to . + [Obsolete] public static IGraphQLBuilder AddErrorInfoProvider(this IGraphQLBuilder builder, Action configureOptions) => AddErrorInfoProvider(builder, configureOptions); @@ -32,6 +68,7 @@ public static IGraphQLBuilder AddErrorInfoProvider(this IGraphQLBuilder builder, /// GraphQL builder used for GraphQL specific extension methods as 'this' argument. /// Action to configure the . /// Reference to . + [Obsolete] public static IGraphQLBuilder AddErrorInfoProvider(this IGraphQLBuilder builder, Action configureOptions) where TErrorInfoProvider : DefaultErrorInfoProvider { @@ -47,6 +84,7 @@ public static IGraphQLBuilder AddErrorInfoProvider(this IGra /// GraphQL builder used for GraphQL specific extension methods as 'this' argument. /// Action to configure the . /// Reference to . + [Obsolete] public static IGraphQLBuilder AddErrorInfoProvider(this IGraphQLBuilder builder, Action configureOptions) => AddErrorInfoProvider(builder, configureOptions); @@ -57,6 +95,7 @@ public static IGraphQLBuilder AddErrorInfoProvider(this IGraphQLBuilder builder, /// GraphQL builder used for GraphQL specific extension methods as 'this' argument. /// Action to configure the . /// Reference to . + [Obsolete] public static IGraphQLBuilder AddErrorInfoProvider(this IGraphQLBuilder builder, Action configureOptions) where TErrorInfoProvider : DefaultErrorInfoProvider { @@ -76,6 +115,7 @@ public static IGraphQLBuilder AddErrorInfoProvider(this IGra /// /// GraphQL builder used for GraphQL specific extension methods as 'this' argument. /// Reference to . + [Obsolete] public static IGraphQLBuilder AddDataLoader(this IGraphQLBuilder builder) { builder.Services.TryAddSingleton(); @@ -90,6 +130,7 @@ public static IGraphQLBuilder AddDataLoader(this IGraphQLBuilder builder) /// GraphQL builder used for GraphQL specific extension methods as 'this' argument. /// The service lifetime to register the GraphQL types with /// Reference to . + [Obsolete] public static IGraphQLBuilder AddGraphTypes( this IGraphQLBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) @@ -102,6 +143,7 @@ public static IGraphQLBuilder AddGraphTypes( /// The type from assembly to register all GraphQL types from /// The service lifetime to register the GraphQL types with /// Reference to . + [Obsolete] public static IGraphQLBuilder AddGraphTypes( this IGraphQLBuilder builder, Type typeFromAssembly, @@ -115,6 +157,7 @@ public static IGraphQLBuilder AddGraphTypes( /// The assembly to register all GraphQL types from /// The service lifetime to register the GraphQL types with /// Reference to . + [Obsolete] public static IGraphQLBuilder AddGraphTypes( this IGraphQLBuilder builder, Assembly assembly, @@ -136,6 +179,7 @@ public static IGraphQLBuilder AddGraphTypes( /// /// GraphQL builder used for GraphQL specific extension methods as 'this' argument. /// Reference to . + [Obsolete] public static IGraphQLBuilder AddRelayGraphTypes(this IGraphQLBuilder builder) { builder diff --git a/src/Core/Extensions/ServiceCollectionExtensions.cs b/src/Core/Extensions/ServiceCollectionExtensions.cs index 88990def..8abe8a52 100644 --- a/src/Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Core/Extensions/ServiceCollectionExtensions.cs @@ -17,6 +17,7 @@ public static class ServiceCollectionExtensions /// /// Collection of registered services. /// GraphQL builder used for GraphQL specific extension chaining. + [Obsolete] public static IGraphQLBuilder AddGraphQL(this IServiceCollection services) => services.AddGraphQL(_ => { }); /// @@ -25,6 +26,7 @@ public static class ServiceCollectionExtensions /// Collection of registered services. /// An action delegate to configure the provided . /// GraphQL builder used for GraphQL specific extension chaining. + [Obsolete] public static IGraphQLBuilder AddGraphQL(this IServiceCollection services, Action configureOptions) { if (configureOptions == null) @@ -42,6 +44,7 @@ public static IGraphQLBuilder AddGraphQL(this IServiceCollection services, Actio /// This delegate provides additional parameter to resolve all necessary dependencies. /// /// GraphQL builder used for GraphQL specific extension chaining. + [Obsolete] public static IGraphQLBuilder AddGraphQL(this IServiceCollection services, Action configureOptions) { if (configureOptions == null) diff --git a/src/Core/GraphQLBuilder.cs b/src/Core/GraphQLBuilder.cs index da5469d6..20b6194e 100644 --- a/src/Core/GraphQLBuilder.cs +++ b/src/Core/GraphQLBuilder.cs @@ -1,7 +1,9 @@ +using System; using Microsoft.Extensions.DependencyInjection; namespace GraphQL.Server { + [Obsolete] internal sealed class GraphQLBuilder : IGraphQLBuilder { public IServiceCollection Services { get; } diff --git a/src/Core/IGraphQLBuilder.cs b/src/Core/IGraphQLBuilder.cs index 7c489008..5c53c96b 100644 --- a/src/Core/IGraphQLBuilder.cs +++ b/src/Core/IGraphQLBuilder.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Extensions.DependencyInjection; namespace GraphQL.Server @@ -5,6 +6,7 @@ namespace GraphQL.Server /// /// GraphQL builder used for GraphQL specific extension methods as 'this' argument. /// + [Obsolete] public interface IGraphQLBuilder { /// diff --git a/src/Transports.AspNetCore.NewtonsoftJson/GraphQLBuilderNewtonsoftJsonExtensions.cs b/src/Transports.AspNetCore.NewtonsoftJson/GraphQLBuilderNewtonsoftJsonExtensions.cs index 5fa22f27..1ad3f610 100644 --- a/src/Transports.AspNetCore.NewtonsoftJson/GraphQLBuilderNewtonsoftJsonExtensions.cs +++ b/src/Transports.AspNetCore.NewtonsoftJson/GraphQLBuilderNewtonsoftJsonExtensions.cs @@ -15,7 +15,7 @@ public static class GraphQLBuilderNewtonsoftJsonExtensions /// Adds a and a /// to the service collection with the provided configuration/settings. /// - /// + /// The . /// /// Action to further configure the request deserializer's settings. /// Affects reading of the JSON from the HTTP request the middleware processes. @@ -25,6 +25,7 @@ public static class GraphQLBuilderNewtonsoftJsonExtensions /// Affects JSON returned by the middleware. /// /// GraphQL Builder. + [Obsolete] public static IGraphQLBuilder AddNewtonsoftJson(this IGraphQLBuilder builder, Action configureDeserializerSettings = null, Action configureSerializerSettings = null) @@ -34,5 +35,29 @@ public static IGraphQLBuilder AddNewtonsoftJson(this IGraphQLBuilder builder, return builder; } + + /// + /// Adds a and a + /// to the service collection with the provided configuration/settings. + /// + /// The . + /// + /// Action to further configure the request deserializer's settings. + /// Affects reading of the JSON from the HTTP request the middleware processes. + /// + /// + /// Action to further configure the response serializer's settings. + /// Affects JSON returned by the middleware. + /// + /// GraphQL Builder. + public static DI.IGraphQLBuilder AddNewtonsoftJson(this DI.IGraphQLBuilder builder, + Action configureDeserializerSettings = null, + Action configureSerializerSettings = null) + { + builder.Register(p => new GraphQLRequestDeserializer(configureDeserializerSettings ?? (_ => { })), DI.ServiceLifetime.Singleton); + NewtonsoftJson.GraphQLBuilderExtensions.AddNewtonsoftJson(builder, configureSerializerSettings); + + return builder; + } } } diff --git a/src/Transports.AspNetCore.NewtonsoftJson/Transports.AspNetCore.NewtonsoftJson.csproj b/src/Transports.AspNetCore.NewtonsoftJson/Transports.AspNetCore.NewtonsoftJson.csproj index 64ee9a84..99eb68d3 100644 --- a/src/Transports.AspNetCore.NewtonsoftJson/Transports.AspNetCore.NewtonsoftJson.csproj +++ b/src/Transports.AspNetCore.NewtonsoftJson/Transports.AspNetCore.NewtonsoftJson.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Transports.AspNetCore.SystemTextJson/GraphQLBuilderSystemTextJsonExtensions.cs b/src/Transports.AspNetCore.SystemTextJson/GraphQLBuilderSystemTextJsonExtensions.cs index a498f141..c51d9e75 100644 --- a/src/Transports.AspNetCore.SystemTextJson/GraphQLBuilderSystemTextJsonExtensions.cs +++ b/src/Transports.AspNetCore.SystemTextJson/GraphQLBuilderSystemTextJsonExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Text.Json; using GraphQL.Execution; using GraphQL.Server.Transports.AspNetCore; @@ -15,7 +16,7 @@ public static class GraphQLBuilderSystemTextJsonExtensions /// Adds a and a /// to the service collection with the provided configuration/settings. /// - /// + /// The . /// /// Action to further configure the request deserializer's settings. /// Affects reading of the JSON from the HTTP request the middleware processes. @@ -25,6 +26,7 @@ public static class GraphQLBuilderSystemTextJsonExtensions /// Affects JSON returned by the middleware. /// /// GraphQL Builder. + [Obsolete] public static IGraphQLBuilder AddSystemTextJson(this IGraphQLBuilder builder, Action configureDeserializerSettings = null, Action configureSerializerSettings = null) @@ -38,5 +40,36 @@ public static IGraphQLBuilder AddSystemTextJson(this IGraphQLBuilder builder, return builder; } + + /// + /// Adds a and a + /// to the service collection with the provided configuration/settings. + /// + /// The . + /// + /// Action to further configure the request deserializer's settings. + /// Affects reading of the JSON from the HTTP request the middleware processes. + /// + /// + /// Action to further configure the response serializer's settings. + /// Affects JSON returned by the middleware. + /// + /// GraphQL Builder. + public static DI.IGraphQLBuilder AddSystemTextJson(this DI.IGraphQLBuilder builder, + Action configureDeserializerSettings = null, + Action configureSerializerSettings = null) + { + builder.Register(services => new GraphQLRequestDeserializer(configureDeserializerSettings ?? (_ => { })), DI.ServiceLifetime.Singleton); + SystemTextJson.GraphQLBuilderExtensions.AddSystemTextJson(builder, configureSerializerSettings); + builder.Configure(opt => + { + if (!opt.Converters.Any(y => y.GetType() == typeof(OperationMessageConverter))) + { + opt.Converters.Add(new OperationMessageConverter()); + } + }); + + return builder; + } } } diff --git a/src/Transports.AspNetCore.SystemTextJson/GraphQLRequestDeserializer.cs b/src/Transports.AspNetCore.SystemTextJson/GraphQLRequestDeserializer.cs index 5cb9eb8d..ccb63f47 100644 --- a/src/Transports.AspNetCore.SystemTextJson/GraphQLRequestDeserializer.cs +++ b/src/Transports.AspNetCore.SystemTextJson/GraphQLRequestDeserializer.cs @@ -24,7 +24,7 @@ public class GraphQLRequestDeserializer : IGraphQLRequestDeserializer public GraphQLRequestDeserializer(Action configure) { // Add converter that deserializes Variables property - _serializerOptions.Converters.Add(new ObjectDictionaryConverter()); + _serializerOptions.Converters.Add(new InputsConverter()); configure?.Invoke(_serializerOptions); } @@ -127,8 +127,8 @@ private static GraphQLRequest ToGraphQLRequest(InternalGraphQLRequest internalGr { OperationName = internalGraphQLRequest.OperationName, Query = internalGraphQLRequest.Query, - Inputs = internalGraphQLRequest.Variables?.ToInputs(), // must return null if not provided, not an empty dictionary - Extensions = internalGraphQLRequest.Extensions?.ToInputs(), // must return null if not provided, not an empty dictionary + Inputs = internalGraphQLRequest.Variables, // must return null if not provided, not an empty dictionary + Extensions = internalGraphQLRequest.Extensions, // must return null if not provided, not an empty dictionary }; } } diff --git a/src/Transports.AspNetCore.SystemTextJson/InternalGraphQLRequest.cs b/src/Transports.AspNetCore.SystemTextJson/InternalGraphQLRequest.cs index 40734dec..c53cb8e5 100644 --- a/src/Transports.AspNetCore.SystemTextJson/InternalGraphQLRequest.cs +++ b/src/Transports.AspNetCore.SystemTextJson/InternalGraphQLRequest.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text.Json.Serialization; namespace GraphQL.Server.Transports.AspNetCore.SystemTextJson @@ -16,16 +15,16 @@ internal sealed class InternalGraphQLRequest /// /// Population of this property during deserialization from JSON requires - /// . + /// . /// [JsonPropertyName(GraphQLRequest.VARIABLES_KEY)] - public Dictionary Variables { get; set; } + public Inputs Variables { get; set; } /// /// Population of this property during deserialization from JSON requires - /// . + /// . /// [JsonPropertyName(GraphQLRequest.EXTENSIONS_KEY)] - public Dictionary Extensions { get; set; } + public Inputs Extensions { get; set; } } } diff --git a/src/Transports.AspNetCore.SystemTextJson/Transports.AspNetCore.SystemTextJson.csproj b/src/Transports.AspNetCore.SystemTextJson/Transports.AspNetCore.SystemTextJson.csproj index b46413b1..6bb99d7a 100644 --- a/src/Transports.AspNetCore.SystemTextJson/Transports.AspNetCore.SystemTextJson.csproj +++ b/src/Transports.AspNetCore.SystemTextJson/Transports.AspNetCore.SystemTextJson.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Transports.AspNetCore/Extensions/GraphQLBuilderUserContextExtensions.cs b/src/Transports.AspNetCore/Extensions/GraphQLBuilderUserContextExtensions.cs index cfd0dc82..c19cb7d6 100644 --- a/src/Transports.AspNetCore/Extensions/GraphQLBuilderUserContextExtensions.cs +++ b/src/Transports.AspNetCore/Extensions/GraphQLBuilderUserContextExtensions.cs @@ -17,6 +17,7 @@ public static class GraphQLBuilderUserContextExtensions /// The type of the implementation. /// The GraphQL builder. /// The GraphQL builder. + [Obsolete] public static IGraphQLBuilder AddUserContextBuilder(this IGraphQLBuilder builder) where TUserContextBuilder : class, IUserContextBuilder { @@ -32,6 +33,7 @@ public static IGraphQLBuilder AddUserContextBuilder(this IG /// The GraphQL builder. /// A delegate used to create the user context from the . /// The GraphQL builder. + [Obsolete] public static IGraphQLBuilder AddUserContextBuilder(this IGraphQLBuilder builder, Func creator) where TUserContext : class, IDictionary { @@ -47,6 +49,7 @@ public static IGraphQLBuilder AddUserContextBuilder(this IGraphQLB /// The GraphQL builder. /// A delegate used to create the user context from the . /// The GraphQL builder. + [Obsolete] public static IGraphQLBuilder AddUserContextBuilder(this IGraphQLBuilder builder, Func> creator) where TUserContext : class, IDictionary { @@ -61,11 +64,94 @@ public static IGraphQLBuilder AddUserContextBuilder(this IGraphQLB /// /// The GraphQL builder. /// The GraphQL builder. + [Obsolete] public static IGraphQLBuilder AddDefaultEndpointSelectorPolicy(this IGraphQLBuilder builder) { builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); return builder; } + + /// + /// Adds an as a singleton. + /// + /// The type of the implementation. + /// The GraphQL builder. + /// The GraphQL builder. + public static DI.IGraphQLBuilder AddUserContextBuilder(this DI.IGraphQLBuilder builder) + where TUserContextBuilder : class, IUserContextBuilder + { + builder.Register(DI.ServiceLifetime.Singleton); + builder.ConfigureExecution(async options => + { + if (options.UserContext == null || options.UserContext.Count == 0 && options.UserContext.GetType() == typeof(Dictionary)) + { + var httpContext = options.RequestServices.GetRequiredService().HttpContext; + var contextBuilder = options.RequestServices.GetRequiredService(); + options.UserContext = await contextBuilder.BuildUserContext(httpContext); + } + }); + + return builder; + } + + /// + /// Set up a delegate to create the UserContext for each GraphQL request + /// + /// + /// The GraphQL builder. + /// A delegate used to create the user context from the . + /// The GraphQL builder. + public static DI.IGraphQLBuilder AddUserContextBuilder(this DI.IGraphQLBuilder builder, Func creator) + where TUserContext : class, IDictionary + { + builder.Register(new UserContextBuilder(creator)); + builder.ConfigureExecution(options => + { + if (options.UserContext == null || options.UserContext.Count == 0 && options.UserContext.GetType() == typeof(Dictionary)) + { + var httpContext = options.RequestServices.GetRequiredService().HttpContext; + options.UserContext = creator(httpContext); + } + }); + + return builder; + } + + /// + /// Set up a delegate to create the UserContext for each GraphQL request + /// + /// + /// The GraphQL builder. + /// A delegate used to create the user context from the . + /// The GraphQL builder. + public static DI.IGraphQLBuilder AddUserContextBuilder(this DI.IGraphQLBuilder builder, Func> creator) + where TUserContext : class, IDictionary + { + builder.Register(new UserContextBuilder(creator)); + builder.ConfigureExecution(async options => + { + if (options.UserContext == null || options.UserContext.Count == 0 && options.UserContext.GetType() == typeof(Dictionary)) + { + var httpContext = options.RequestServices.GetRequiredService().HttpContext; + options.UserContext = await creator(httpContext); + } + }); + + return builder; + } + + /// + /// Set up default policy for matching endpoints. It is required when both GraphQL HTTP and + /// GraphQL WebSockets middlewares are mapped to the same endpoint (by default 'graphql'). + /// + /// The GraphQL builder. + /// The GraphQL builder. + public static DI.IGraphQLBuilder AddDefaultEndpointSelectorPolicy(this DI.IGraphQLBuilder builder) + { + builder.TryRegister(DI.ServiceLifetime.Singleton); + + return builder; + } } } diff --git a/src/Transports.Subscriptions.Abstractions/Transports.Subscriptions.Abstractions.csproj b/src/Transports.Subscriptions.Abstractions/Transports.Subscriptions.Abstractions.csproj index 7bc56b26..e38830bb 100644 --- a/src/Transports.Subscriptions.Abstractions/Transports.Subscriptions.Abstractions.csproj +++ b/src/Transports.Subscriptions.Abstractions/Transports.Subscriptions.Abstractions.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/src/Transports.Subscriptions.WebSockets/Extensions/GraphQLBuilderWebSocketsExtensions.cs b/src/Transports.Subscriptions.WebSockets/Extensions/GraphQLBuilderWebSocketsExtensions.cs index b54713cb..ccbab258 100644 --- a/src/Transports.Subscriptions.WebSockets/Extensions/GraphQLBuilderWebSocketsExtensions.cs +++ b/src/Transports.Subscriptions.WebSockets/Extensions/GraphQLBuilderWebSocketsExtensions.cs @@ -1,3 +1,4 @@ +using System; using GraphQL.Server.Transports.Subscriptions.Abstractions; using GraphQL.Server.Transports.WebSockets; using Microsoft.Extensions.DependencyInjection; @@ -9,8 +10,7 @@ public static class GraphQLBuilderWebSocketsExtensions /// /// Add required services for GraphQL web sockets /// - /// - /// + [Obsolete] public static IGraphQLBuilder AddWebSockets(this IGraphQLBuilder builder) { builder.Services @@ -20,5 +20,18 @@ public static IGraphQLBuilder AddWebSockets(this IGraphQLBuilder builder) return builder; } + + /// + /// Add required services for GraphQL web sockets + /// + public static DI.IGraphQLBuilder AddWebSockets(this DI.IGraphQLBuilder builder) + { + builder + .Register(typeof(IWebSocketConnectionFactory<>), typeof(WebSocketConnectionFactory<>), DI.ServiceLifetime.Transient) + .Register(DI.ServiceLifetime.Transient) + .Register(DI.ServiceLifetime.Transient); + + return builder; + } } } diff --git a/tests/ApiApprovalTests/GraphQL.Server.Authorization.AspNetCore.approved.txt b/tests/ApiApprovalTests/GraphQL.Server.Authorization.AspNetCore.approved.txt index f16a8d62..f9fbda7b 100644 --- a/tests/ApiApprovalTests/GraphQL.Server.Authorization.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/GraphQL.Server.Authorization.AspNetCore.approved.txt @@ -36,7 +36,11 @@ namespace GraphQL.Server { public static class GraphQLBuilderAuthorizationExtensions { + public static GraphQL.DI.IGraphQLBuilder AddGraphQLAuthorization(this GraphQL.DI.IGraphQLBuilder builder) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddGraphQLAuthorization(this GraphQL.Server.IGraphQLBuilder builder) { } + public static GraphQL.DI.IGraphQLBuilder AddGraphQLAuthorization(this GraphQL.DI.IGraphQLBuilder builder, System.Action? configure) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddGraphQLAuthorization(this GraphQL.Server.IGraphQLBuilder builder, System.Action options) { } } } \ No newline at end of file diff --git a/tests/ApiApprovalTests/GraphQL.Server.Core.approved.txt b/tests/ApiApprovalTests/GraphQL.Server.Core.approved.txt index 26e7f207..bc9da907 100644 --- a/tests/ApiApprovalTests/GraphQL.Server.Core.approved.txt +++ b/tests/ApiApprovalTests/GraphQL.Server.Core.approved.txt @@ -1,9 +1,18 @@ namespace GraphQL.Server { + public class BasicGraphQLExecuter : GraphQL.Server.IGraphQLExecuter, GraphQL.Server.IGraphQLExecuter + where TSchema : GraphQL.Types.ISchema + { + public BasicGraphQLExecuter(TSchema schema, GraphQL.IDocumentExecuter documentExecuter, Microsoft.Extensions.Options.IOptions options) { } + public TSchema Schema { get; } + public virtual System.Threading.Tasks.Task ExecuteAsync(string operationName, string query, GraphQL.Inputs variables, System.Collections.Generic.IDictionary context, System.IServiceProvider requestServices, System.Threading.CancellationToken cancellationToken = default) { } + protected virtual GraphQL.ExecutionOptions GetOptions(string operationName, string query, GraphQL.Inputs variables, System.Collections.Generic.IDictionary context, System.IServiceProvider requestServices, System.Threading.CancellationToken cancellationToken) { } + } public class DefaultErrorInfoProvider : GraphQL.Execution.ErrorInfoProvider { public DefaultErrorInfoProvider(Microsoft.Extensions.Options.IOptions options) { } } + [System.Obsolete] public class DefaultGraphQLExecuter : GraphQL.Server.IGraphQLExecuter, GraphQL.Server.IGraphQLExecuter where TSchema : GraphQL.Types.ISchema { @@ -14,17 +23,28 @@ namespace GraphQL.Server } public static class GraphQLBuilderCoreExtensions { + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddDataLoader(this GraphQL.Server.IGraphQLBuilder builder) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddErrorInfoProvider(this GraphQL.Server.IGraphQLBuilder builder, System.Action configureOptions) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddErrorInfoProvider(this GraphQL.Server.IGraphQLBuilder builder, System.Action configureOptions) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddErrorInfoProvider(this GraphQL.Server.IGraphQLBuilder builder, System.Action configureOptions) where TErrorInfoProvider : GraphQL.Server.DefaultErrorInfoProvider { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddErrorInfoProvider(this GraphQL.Server.IGraphQLBuilder builder, System.Action configureOptions) where TErrorInfoProvider : GraphQL.Server.DefaultErrorInfoProvider { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddGraphTypes(this GraphQL.Server.IGraphQLBuilder builder, Microsoft.Extensions.DependencyInjection.ServiceLifetime serviceLifetime = 0) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddGraphTypes(this GraphQL.Server.IGraphQLBuilder builder, System.Reflection.Assembly assembly, Microsoft.Extensions.DependencyInjection.ServiceLifetime serviceLifetime = 0) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddGraphTypes(this GraphQL.Server.IGraphQLBuilder builder, System.Type typeFromAssembly, Microsoft.Extensions.DependencyInjection.ServiceLifetime serviceLifetime = 0) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddRelayGraphTypes(this GraphQL.Server.IGraphQLBuilder builder) { } + public static GraphQL.DI.IGraphQLBuilder AddServer(this GraphQL.DI.IGraphQLBuilder builder, bool installMetricsMiddleware, System.Action? configureOptions) { } + public static GraphQL.DI.IGraphQLBuilder AddServer(this GraphQL.DI.IGraphQLBuilder builder, bool installMetricsMiddleware, System.Action? configureOptions = null) { } } public class GraphQLOptions { @@ -46,6 +66,7 @@ namespace GraphQL.Server public string OperationName { get; set; } public string Query { get; set; } } + [System.Obsolete] public interface IGraphQLBuilder { Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; } @@ -64,8 +85,11 @@ namespace Microsoft.Extensions.DependencyInjection { public static class ServiceCollectionExtensions { + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddGraphQL(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddGraphQL(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddGraphQL(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { } } } \ No newline at end of file diff --git a/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.NewtonsoftJson.approved.txt b/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.NewtonsoftJson.approved.txt index 7477da20..44e1d0fd 100644 --- a/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.NewtonsoftJson.approved.txt +++ b/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.NewtonsoftJson.approved.txt @@ -2,6 +2,8 @@ namespace GraphQL.Server { public static class GraphQLBuilderNewtonsoftJsonExtensions { + public static GraphQL.DI.IGraphQLBuilder AddNewtonsoftJson(this GraphQL.DI.IGraphQLBuilder builder, System.Action configureDeserializerSettings = null, System.Action configureSerializerSettings = null) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddNewtonsoftJson(this GraphQL.Server.IGraphQLBuilder builder, System.Action configureDeserializerSettings = null, System.Action configureSerializerSettings = null) { } } } diff --git a/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.SystemTextJson.approved.txt b/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.SystemTextJson.approved.txt index d0fd2487..215a9497 100644 --- a/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.SystemTextJson.approved.txt +++ b/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.SystemTextJson.approved.txt @@ -2,6 +2,8 @@ namespace GraphQL.Server { public static class GraphQLBuilderSystemTextJsonExtensions { + public static GraphQL.DI.IGraphQLBuilder AddSystemTextJson(this GraphQL.DI.IGraphQLBuilder builder, System.Action configureDeserializerSettings = null, System.Action configureSerializerSettings = null) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddSystemTextJson(this GraphQL.Server.IGraphQLBuilder builder, System.Action configureDeserializerSettings = null, System.Action configureSerializerSettings = null) { } } } diff --git a/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.approved.txt index 3d66a4ca..d9fc6618 100644 --- a/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -2,11 +2,22 @@ namespace GraphQL.Server { public static class GraphQLBuilderUserContextExtensions { + public static GraphQL.DI.IGraphQLBuilder AddDefaultEndpointSelectorPolicy(this GraphQL.DI.IGraphQLBuilder builder) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddDefaultEndpointSelectorPolicy(this GraphQL.Server.IGraphQLBuilder builder) { } + public static GraphQL.DI.IGraphQLBuilder AddUserContextBuilder(this GraphQL.DI.IGraphQLBuilder builder) + where TUserContextBuilder : class, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddUserContextBuilder(this GraphQL.Server.IGraphQLBuilder builder) where TUserContextBuilder : class, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder { } + public static GraphQL.DI.IGraphQLBuilder AddUserContextBuilder(this GraphQL.DI.IGraphQLBuilder builder, System.Func> creator) + where TUserContext : class, System.Collections.Generic.IDictionary { } + public static GraphQL.DI.IGraphQLBuilder AddUserContextBuilder(this GraphQL.DI.IGraphQLBuilder builder, System.Func creator) + where TUserContext : class, System.Collections.Generic.IDictionary { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddUserContextBuilder(this GraphQL.Server.IGraphQLBuilder builder, System.Func> creator) where TUserContext : class, System.Collections.Generic.IDictionary { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddUserContextBuilder(this GraphQL.Server.IGraphQLBuilder builder, System.Func creator) where TUserContext : class, System.Collections.Generic.IDictionary { } } diff --git a/tests/ApiApprovalTests/GraphQL.Server.Transports.Subscriptions.WebSockets.approved.txt b/tests/ApiApprovalTests/GraphQL.Server.Transports.Subscriptions.WebSockets.approved.txt index 9190e318..1bd33b1c 100644 --- a/tests/ApiApprovalTests/GraphQL.Server.Transports.Subscriptions.WebSockets.approved.txt +++ b/tests/ApiApprovalTests/GraphQL.Server.Transports.Subscriptions.WebSockets.approved.txt @@ -2,6 +2,8 @@ namespace GraphQL.Server { public static class GraphQLBuilderWebSocketsExtensions { + public static GraphQL.DI.IGraphQLBuilder AddWebSockets(this GraphQL.DI.IGraphQLBuilder builder) { } + [System.Obsolete] public static GraphQL.Server.IGraphQLBuilder AddWebSockets(this GraphQL.Server.IGraphQLBuilder builder) { } } } diff --git a/tests/Transports.Subscriptions.Abstractions.Tests/Specs/ChatSpec.cs b/tests/Transports.Subscriptions.Abstractions.Tests/Specs/ChatSpec.cs index 5464954c..87b29783 100644 --- a/tests/Transports.Subscriptions.Abstractions.Tests/Specs/ChatSpec.cs +++ b/tests/Transports.Subscriptions.Abstractions.Tests/Specs/ChatSpec.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using GraphQL.Execution; using GraphQL.Samples.Schemas.Chat; -using GraphQL.Validation; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Newtonsoft.Json.Linq; @@ -21,12 +20,10 @@ public ChatSpec() _transportReader = _transport.Reader as TestableReader; _transportWriter = _transport.Writer as TestableWriter; _subscriptions = new SubscriptionManager( - new DefaultGraphQLExecuter( + new BasicGraphQLExecuter( new ChatSchema(_chat, new DefaultServiceProvider()), new SubscriptionDocumentExecuter(), - Options.Create(new GraphQLOptions { }), - Enumerable.Empty(), - Enumerable.Empty() + Options.Create(new GraphQLOptions { }) ), new NullLoggerFactory()); diff --git a/tests/Transports.Subscriptions.WebSockets.Tests/TestStartup.cs b/tests/Transports.Subscriptions.WebSockets.Tests/TestStartup.cs index 78471d2e..3acbd5e2 100644 --- a/tests/Transports.Subscriptions.WebSockets.Tests/TestStartup.cs +++ b/tests/Transports.Subscriptions.WebSockets.Tests/TestStartup.cs @@ -9,8 +9,10 @@ public class TestStartup public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); +#pragma warning disable CS0612 // Type or member is obsolete services.AddGraphQL() .AddWebSockets(); +#pragma warning restore CS0612 // Type or member is obsolete services.AddLogging(builder => { // prevent writing errors to Console.Error during tests (required for testing on ubuntu)