diff --git a/GraphQL.Server.sln b/GraphQL.Server.sln index 4775b9a1..d7d8c759 100644 --- a/GraphQL.Server.sln +++ b/GraphQL.Server.sln @@ -124,6 +124,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Upload.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.NativeAot", "samples\Samples.NativeAot\Samples.NativeAot.csproj", "{56042483-2E36-41DF-9DC4-71DC527A36E4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.HttpResult", "samples\Samples.HttpResult\Samples.HttpResult.csproj", "{C457FCA0-217D-91E4-5C76-5E37DE4C4CB0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.HttpResult.Tests", "tests\Samples.HttpResult.Tests\Samples.HttpResult.Tests.csproj", "{F23D0C12-3E26-61F3-2A5E-279D3F611F2E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -270,6 +274,14 @@ Global {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 + {C457FCA0-217D-91E4-5C76-5E37DE4C4CB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C457FCA0-217D-91E4-5C76-5E37DE4C4CB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C457FCA0-217D-91E4-5C76-5E37DE4C4CB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C457FCA0-217D-91E4-5C76-5E37DE4C4CB0}.Release|Any CPU.Build.0 = Release|Any CPU + {F23D0C12-3E26-61F3-2A5E-279D3F611F2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F23D0C12-3E26-61F3-2A5E-279D3F611F2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F23D0C12-3E26-61F3-2A5E-279D3F611F2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F23D0C12-3E26-61F3-2A5E-279D3F611F2E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -311,6 +323,8 @@ Global {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} + {C457FCA0-217D-91E4-5C76-5E37DE4C4CB0} = {5C07AFA3-12F2-40EA-807D-7A1EEF29012B} + {F23D0C12-3E26-61F3-2A5E-279D3F611F2E} = {BBD07745-C962-4D2D-B302-6DA1BCC2FF43} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3FC7FA59-E938-453C-8C4A-9D5635A9489A} diff --git a/README.md b/README.md index 60506fb8..2529f56a 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,11 @@ as defined in the [apollographql/subscriptions-transport-ws](https://github.com/ and [enisdenjo/graphql-ws](https://github.com/enisdenjo/graphql-ws) repositories, respectively. The middleware can be configured through the `IApplicationBuilder` or `IEndpointRouteBuilder` -builder interfaces. In addition, an `ExecutionResultActionResult` class is added for returning -`ExecutionResult` instances directly from a controller action. +builder interfaces. Alternatively, route handlers (such as `MapGet` and `MapPost`) can return +a `GraphQLExecutionHttpResult` for direct GraphQL execution, or `ExecutionResultHttpResult` for +returning pre-executed GraphQL responses. Similarly, `GraphQLExecutionActionResult` +and `ExecutionResultActionResult` classes can be used to return GraphQL responses from +controller actions. Authorization is also supported with the included `AuthorizationValidationRule`. It will scan GraphQL documents and validate that the schema and all referenced output graph types, fields of @@ -211,6 +214,50 @@ app.UseEndpoints(endpoints => await app.RunAsync(); ``` +### Configuration with route handlers (.NET 6+) + +Although not recommended, you may set up [route handlers](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/route-handlers) +to execute GraphQL requests using `MapGet` and `MapPost` that return an `IResult`. +You will not need `UseGraphQL` or `MapGraphQL` in the application startup. Note that GET must be +mapped to support WebSocket connections, as WebSocket connections upgrade from HTTP GET requests. + +#### Using `GraphQLExecutionHttpResult` + +```csharp +var app = builder.Build(); +app.UseDeveloperExceptionPage(); +app.UseWebSockets(); + +// configure the graphql endpoint at "/graphql", using GraphQLExecutionHttpResult +// map GET in order to support both GET and WebSocket requests +app.MapGet("/graphql", () => new GraphQLExecutionHttpResult()); +// map POST to handle standard GraphQL POST requests +app.MapPost("/graphql", () => new GraphQLExecutionHttpResult()); + +await app.RunAsync(); +``` + +#### Using `ExecutionResultHttpResult` + +```csharp +app.MapPost("/graphql", async (HttpContext context, IDocumentExecuter documentExecuter, IGraphQLSerializer serializer) => +{ + var request = await serializer.ReadAsync(context.Request.Body, context.RequestAborted); + var opts = new ExecutionOptions + { + Query = request?.Query, + DocumentId = request?.DocumentId, + Variables = request?.Variables, + Extensions = request?.Extensions, + CancellationToken = context.RequestAborted, + RequestServices = context.RequestServices, + User = context.User, + }; + + return new ExecutionResultHttpResult(await documentExecuter.ExecuteAsync(opts)); +}); +``` + ### Configuration with a MVC controller Although not recommended, you may set up a controller action to execute GraphQL @@ -1136,6 +1183,7 @@ typical ASP.NET Core scenarios. | Controller | .NET 8 Minimal | MVC implementation; does not include WebSocket support | | Cors | .NET 8 Minimal | Demonstrates configuring a GraphQL endpoint to use a specified CORS policy | | EndpointRouting | .NET 8 Minimal | Demonstrates configuring GraphQL through endpoint routing | +| HttpResult | .NET 8 Minimal | Demonstrates using `MapGet` and/or `MapPost` to return a GraphQL response | | 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 | diff --git a/samples/Samples.Controller/Controllers/HomeController.cs b/samples/Samples.Controller/Controllers/HomeController.cs index 4f249c1d..e7998779 100644 --- a/samples/Samples.Controller/Controllers/HomeController.cs +++ b/samples/Samples.Controller/Controllers/HomeController.cs @@ -37,7 +37,7 @@ public IActionResult GraphQLAsync() /******* Sample with custom logic only using ExecutionResultActionResult to return the result ********/ [HttpGet] [ActionName("graphql2")] - public Task GraphQL2GetAsync(string query, string? operationName) + public Task GraphQL2GetAsync(string query, string? documentId, string? operationName) { if (HttpContext.WebSockets.IsWebSocketRequest) { @@ -45,7 +45,7 @@ public Task GraphQL2GetAsync(string query, string? operationName) } else { - return ExecuteGraphQLRequestAsync(BuildRequest(query, operationName)); + return ExecuteGraphQLRequestAsync(BuildRequest(query, documentId, operationName)); } } @@ -56,7 +56,7 @@ public async Task GraphQL2PostAsync() if (HttpContext.Request.HasFormContentType) { var form = await HttpContext.Request.ReadFormAsync(HttpContext.RequestAborted); - return await ExecuteGraphQLRequestAsync(BuildRequest(form["query"].ToString(), form["operationName"].ToString(), form["variables"].ToString(), form["extensions"].ToString())); + return await ExecuteGraphQLRequestAsync(BuildRequest(form["query"].ToString(), form["documentId"].ToString(), form["operationName"].ToString(), form["variables"].ToString(), form["extensions"].ToString())); } else if (HttpContext.Request.HasJsonContentType()) { @@ -66,10 +66,11 @@ public async Task GraphQL2PostAsync() return BadRequest(); } - private GraphQLRequest BuildRequest(string query, string? operationName, string? variables = null, string? extensions = null) + private GraphQLRequest BuildRequest(string query, string? documentId, string? operationName, string? variables = null, string? extensions = null) => new GraphQLRequest { Query = query == "" ? null : query, + DocumentId = documentId == "" ? null : documentId, OperationName = operationName == "" ? null : operationName, Variables = _serializer.Deserialize(variables == "" ? null : variables), Extensions = _serializer.Deserialize(extensions == "" ? null : extensions), @@ -82,6 +83,7 @@ private async Task ExecuteGraphQLRequestAsync(GraphQLRequest? req var opts = new ExecutionOptions { Query = request?.Query, + DocumentId = request?.DocumentId, OperationName = request?.OperationName, Variables = request?.Variables, Extensions = request?.Extensions, diff --git a/samples/Samples.HttpResult/Program.cs b/samples/Samples.HttpResult/Program.cs new file mode 100644 index 00000000..cbad1217 --- /dev/null +++ b/samples/Samples.HttpResult/Program.cs @@ -0,0 +1,77 @@ +using GraphQL; +using GraphQL.Server.Transports.AspNetCore; +using GraphQL.Transport; +using GraphQL.Types; +using Chat = GraphQL.Samples.Schemas.Chat; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSingleton(); +builder.Services.AddGraphQL(b => b + .AddAutoSchema(s => s + .WithMutation() + .WithSubscription()) + .AddSystemTextJson()); + +var app = builder.Build(); +app.UseDeveloperExceptionPage(); +app.UseWebSockets(); + +// configure the graphql endpoint at "/graphql", using GraphQLExecutionHttpResult +// map GET in order to support both GET and WebSocket requests +app.MapGet("/graphql", () => new GraphQLExecutionHttpResult()); +// map POST to handle standard GraphQL POST requests +app.MapPost("/graphql", () => new GraphQLExecutionHttpResult()); + +// Example endpoint demonstrating ExecutionResultHttpResult with custom logic +app.MapPost("/graphql-result", async (HttpContext context, IDocumentExecuter documentExecuter, IGraphQLTextSerializer serializer) => +{ + GraphQLRequest? request; + + if (context.Request.HasFormContentType) + { + var form = await context.Request.ReadFormAsync(context.RequestAborted); + request = new GraphQLRequest + { + Query = form["query"].ToString() == "" ? null : form["query"].ToString(), + DocumentId = form["documentId"].ToString() == "" ? null : form["documentId"].ToString(), + OperationName = form["operationName"].ToString() == "" ? null : form["operationName"].ToString(), + Variables = serializer.Deserialize(form["variables"].ToString() == "" ? null : form["variables"].ToString()), + Extensions = serializer.Deserialize(form["extensions"].ToString() == "" ? null : form["extensions"].ToString()), + }; + } + else if (context.Request.HasJsonContentType()) + { + request = await serializer.ReadAsync(context.Request.Body, context.RequestAborted); + } + else + { + return Results.BadRequest(); + } + + var opts = new ExecutionOptions + { + Query = request?.Query, + DocumentId = request?.DocumentId, + OperationName = request?.OperationName, + Variables = request?.Variables, + Extensions = request?.Extensions, + CancellationToken = context.RequestAborted, + RequestServices = context.RequestServices, + User = context.User, + }; + + var result = await documentExecuter.ExecuteAsync(opts); + return new ExecutionResultHttpResult(result); +}); + +// configure GraphiQL at "/" +app.UseGraphQLGraphiQL( + "/", + new GraphQL.Server.Ui.GraphiQL.GraphiQLOptions + { + GraphQLEndPoint = "/graphql", + SubscriptionsEndPoint = "/graphql", + }); + +await app.RunAsync(); diff --git a/samples/Samples.HttpResult/Properties/launchSettings.json b/samples/Samples.HttpResult/Properties/launchSettings.json new file mode 100644 index 00000000..87d47d46 --- /dev/null +++ b/samples/Samples.HttpResult/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:51526/", + "sslPort": 44334 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Typical": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/samples/Samples.HttpResult/Samples.HttpResult.csproj b/samples/Samples.HttpResult/Samples.HttpResult.csproj new file mode 100644 index 00000000..974f10a9 --- /dev/null +++ b/samples/Samples.HttpResult/Samples.HttpResult.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/src/Transports.AspNetCore/Compatibility/NullHostApplicationLifetime.cs b/src/Transports.AspNetCore/Compatibility/NullHostApplicationLifetime.cs new file mode 100644 index 00000000..3f39a179 --- /dev/null +++ b/src/Transports.AspNetCore/Compatibility/NullHostApplicationLifetime.cs @@ -0,0 +1,18 @@ +namespace GraphQL.Server.Transports.AspNetCore; + +internal class NullHostApplicationLifetime : IHostApplicationLifetime +{ + private NullHostApplicationLifetime() + { + } + + public static NullHostApplicationLifetime Instance { get; } = new(); + + public CancellationToken ApplicationStarted => default; + + public CancellationToken ApplicationStopped => default; + + public CancellationToken ApplicationStopping => default; + + public void StopApplication() { } +} diff --git a/src/Transports.AspNetCore/ExecutionResultHttpResult.cs b/src/Transports.AspNetCore/ExecutionResultHttpResult.cs new file mode 100644 index 00000000..0b15c6ce --- /dev/null +++ b/src/Transports.AspNetCore/ExecutionResultHttpResult.cs @@ -0,0 +1,39 @@ +#if NET6_0_OR_GREATER +namespace GraphQL.Server.Transports.AspNetCore; + +/// +/// An that formats the as JSON. +/// +public sealed class ExecutionResultHttpResult : IResult +{ + private readonly ExecutionResult _executionResult; + private readonly HttpStatusCode _statusCode; + + /// + public ExecutionResultHttpResult(ExecutionResult executionResult) + { + _executionResult = executionResult; + _statusCode = executionResult.Executed ? HttpStatusCode.OK : HttpStatusCode.BadRequest; + } + + /// + public ExecutionResultHttpResult(ExecutionResult executionResult, HttpStatusCode statusCode) + { + _executionResult = executionResult; + _statusCode = statusCode; + } + + /// + public string ContentType { get; set; } = GraphQLHttpMiddleware.CONTENTTYPE_GRAPHQLRESPONSEJSON; + + /// + public async Task ExecuteAsync(HttpContext httpContext) + { + var serializer = httpContext.RequestServices.GetRequiredService(); + var response = httpContext.Response; + response.ContentType = ContentType; + response.StatusCode = (int)_statusCode; + await serializer.WriteAsync(response.Body, _executionResult, httpContext.RequestAborted); + } +} +#endif diff --git a/src/Transports.AspNetCore/GraphQLExecutionActionResult.cs b/src/Transports.AspNetCore/GraphQLExecutionActionResult.cs index 67ad72db..9a65672d 100644 --- a/src/Transports.AspNetCore/GraphQLExecutionActionResult.cs +++ b/src/Transports.AspNetCore/GraphQLExecutionActionResult.cs @@ -49,23 +49,6 @@ public virtual Task ExecuteResultAsync(ActionContext context) return middleware.InvokeAsync(context.HttpContext); } - - private class NullHostApplicationLifetime : IHostApplicationLifetime - { - private NullHostApplicationLifetime() - { - } - - public static NullHostApplicationLifetime Instance { get; } = new(); - - public CancellationToken ApplicationStarted => default; - - public CancellationToken ApplicationStopped => default; - - public CancellationToken ApplicationStopping => default; - - public void StopApplication() { } - } } /// diff --git a/src/Transports.AspNetCore/GraphQLExecutionHttpResult.cs b/src/Transports.AspNetCore/GraphQLExecutionHttpResult.cs new file mode 100644 index 00000000..c81b676b --- /dev/null +++ b/src/Transports.AspNetCore/GraphQLExecutionHttpResult.cs @@ -0,0 +1,72 @@ +#if NET6_0_OR_GREATER +namespace GraphQL.Server.Transports.AspNetCore; + +/// +/// An that executes a GraphQL request for the specified schema. +/// +public class GraphQLExecutionHttpResult : IResult + where TSchema : ISchema +{ + private readonly GraphQLHttpMiddlewareOptions _options; + + /// + /// Initializes a new instance with an optional middleware configuration delegate. + /// + public GraphQLExecutionHttpResult(Action? configure = null) + : this(Configure(configure)) + { + } + + private static GraphQLHttpMiddlewareOptions Configure(Action? configure) + { + var options = new GraphQLHttpMiddlewareOptions(); + configure?.Invoke(options); + return options; + } + + /// + /// Initializes a new instance with the specified middleware options. + /// + public GraphQLExecutionHttpResult(GraphQLHttpMiddlewareOptions options) + { + _options = options; + } + + /// + public virtual Task ExecuteAsync(HttpContext httpContext) + { + var provider = httpContext.RequestServices; + var middleware = new GraphQLHttpMiddleware( + static httpContext => + { + httpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + return Task.CompletedTask; + }, + provider.GetRequiredService(), + provider.GetRequiredService>(), + provider.GetRequiredService(), + _options, + provider.GetService() ?? NullHostApplicationLifetime.Instance); + + return middleware.InvokeAsync(httpContext); + } +} + +/// +/// An that executes a GraphQL request for the default schema. +/// +public class GraphQLExecutionHttpResult : GraphQLExecutionHttpResult +{ + /// + public GraphQLExecutionHttpResult(Action? configure = null) + : base(configure) + { + } + + /// + public GraphQLExecutionHttpResult(GraphQLHttpMiddlewareOptions options) + : base(options) + { + } +} +#endif diff --git a/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/net50/GraphQL.Server.Transports.AspNetCore.approved.txt similarity index 100% rename from tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt rename to tests/ApiApprovalTests/net50/GraphQL.Server.Transports.AspNetCore.approved.txt diff --git a/tests/ApiApprovalTests/net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt new file mode 100644 index 00000000..3b312820 --- /dev/null +++ b/tests/ApiApprovalTests/net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -0,0 +1,531 @@ +namespace GraphQL.Server.Transports.AspNetCore +{ + public static class AuthorizationHelper + { + public static System.Threading.Tasks.ValueTask AuthorizeAsync(GraphQL.Server.Transports.AspNetCore.AuthorizationParameters options, TState state) { } + } + public readonly struct AuthorizationParameters + { + public AuthorizationParameters(Microsoft.AspNetCore.Http.HttpContext httpContext, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, System.Func? onNotAuthenticated, System.Func? onNotAuthorizedRole, System.Func? onNotAuthorizedPolicy) { } + public bool AuthorizationRequired { get; } + public string? AuthorizedPolicy { get; } + public System.Collections.Generic.IEnumerable? AuthorizedRoles { get; } + public Microsoft.AspNetCore.Http.HttpContext HttpContext { get; } + public System.Func? OnNotAuthenticated { get; } + public System.Func? OnNotAuthorizedPolicy { get; } + public System.Func? OnNotAuthorizedRole { get; } + } + public class AuthorizationValidationRule : GraphQL.Validation.ValidationRuleBase + { + public AuthorizationValidationRule() { } + public override System.Threading.Tasks.ValueTask GetPreNodeVisitorAsync(GraphQL.Validation.ValidationContext context) { } + } + public class AuthorizationVisitor : GraphQL.Server.Transports.AspNetCore.AuthorizationVisitorBase + { + public AuthorizationVisitor(GraphQL.Validation.ValidationContext context, System.Security.Claims.ClaimsPrincipal claimsPrincipal, Microsoft.AspNetCore.Authorization.IAuthorizationService authorizationService) { } + protected Microsoft.AspNetCore.Authorization.IAuthorizationService AuthorizationService { get; } + protected System.Security.Claims.ClaimsPrincipal ClaimsPrincipal { get; } + protected override bool IsAuthenticated { get; } + protected override System.Threading.Tasks.ValueTask AuthorizeAsync(string policy) { } + protected override bool IsInRole(string role) { } + } + public abstract class AuthorizationVisitorBase : GraphQL.Validation.INodeVisitor + { + public AuthorizationVisitorBase(GraphQL.Validation.ValidationContext context) { } + protected abstract bool IsAuthenticated { get; } + protected abstract System.Threading.Tasks.ValueTask AuthorizeAsync(string policy); + public virtual System.Threading.Tasks.ValueTask EnterAsync(GraphQLParser.AST.ASTNode node, GraphQL.Validation.ValidationContext context) { } + protected virtual string GenerateResourceDescription(GraphQL.Server.Transports.AspNetCore.AuthorizationVisitorBase.ValidationInfo info) { } + protected virtual void HandleNodeNotAuthorized(GraphQL.Server.Transports.AspNetCore.AuthorizationVisitorBase.ValidationInfo info) { } + protected virtual void HandleNodeNotInPolicy(GraphQL.Server.Transports.AspNetCore.AuthorizationVisitorBase.ValidationInfo info, string policy, Microsoft.AspNetCore.Authorization.AuthorizationResult authorizationResult) { } + protected virtual void HandleNodeNotInRoles(GraphQL.Server.Transports.AspNetCore.AuthorizationVisitorBase.ValidationInfo info, System.Collections.Generic.List roles) { } + protected abstract bool IsInRole(string role); + public virtual System.Threading.Tasks.ValueTask LeaveAsync(GraphQLParser.AST.ASTNode node, GraphQL.Validation.ValidationContext context) { } + protected virtual System.Threading.Tasks.ValueTask ValidateAsync(GraphQL.Server.Transports.AspNetCore.AuthorizationVisitorBase.ValidationInfo info) { } + public virtual System.Threading.Tasks.ValueTask ValidateSchemaAsync(GraphQL.Validation.ValidationContext context) { } + public readonly struct ValidationInfo : System.IEquatable + { + public ValidationInfo(GraphQL.Types.IMetadataReader Obj, GraphQLParser.AST.ASTNode? Node, GraphQL.Types.IFieldType? ParentFieldType, GraphQL.Types.IGraphType? ParentGraphType, GraphQL.Validation.ValidationContext Context) { } + public GraphQL.Validation.ValidationContext Context { get; init; } + public GraphQLParser.AST.ASTNode? Node { get; init; } + public GraphQL.Types.IMetadataReader Obj { get; init; } + public GraphQL.Types.IFieldType? ParentFieldType { get; init; } + public GraphQL.Types.IGraphType? ParentGraphType { get; init; } + } + } + public sealed class ExecutionResultActionResult : Microsoft.AspNetCore.Mvc.IActionResult + { + public ExecutionResultActionResult(GraphQL.ExecutionResult executionResult) { } + public ExecutionResultActionResult(GraphQL.ExecutionResult executionResult, System.Net.HttpStatusCode statusCode) { } + public string ContentType { get; set; } + public System.Threading.Tasks.Task ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) { } + } + public sealed class ExecutionResultHttpResult : Microsoft.AspNetCore.Http.IResult + { + public ExecutionResultHttpResult(GraphQL.ExecutionResult executionResult) { } + public ExecutionResultHttpResult(GraphQL.ExecutionResult executionResult, System.Net.HttpStatusCode statusCode) { } + public string ContentType { get; set; } + public System.Threading.Tasks.Task ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext httpContext) { } + } + public class FormFileGraphType : GraphQL.Types.ScalarGraphType + { + public FormFileGraphType() { } + public override bool CanParseLiteral(GraphQLParser.AST.GraphQLValue value) { } + public override bool CanParseValue(object? value) { } + public override bool IsValidDefault(object value) { } + public override object? ParseLiteral(GraphQLParser.AST.GraphQLValue value) { } + public override object? ParseValue(object? value) { } + public override object? Serialize(object? value) { } + public override GraphQLParser.AST.GraphQLValue ToAST(object? value) { } + } + public class GraphQLExecutionActionResult : GraphQL.Server.Transports.AspNetCore.GraphQLExecutionActionResult + { + public GraphQLExecutionActionResult(GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddlewareOptions options) { } + public GraphQLExecutionActionResult(System.Action? configure = null) { } + } + public class GraphQLExecutionActionResult : Microsoft.AspNetCore.Mvc.IActionResult + where TSchema : GraphQL.Types.ISchema + { + public GraphQLExecutionActionResult(GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddlewareOptions options) { } + public GraphQLExecutionActionResult(System.Action? configure = null) { } + public virtual System.Threading.Tasks.Task ExecuteResultAsync(Microsoft.AspNetCore.Mvc.ActionContext context) { } + } + public class GraphQLExecutionHttpResult : GraphQL.Server.Transports.AspNetCore.GraphQLExecutionHttpResult + { + public GraphQLExecutionHttpResult(GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddlewareOptions options) { } + public GraphQLExecutionHttpResult(System.Action? configure = null) { } + } + public class GraphQLExecutionHttpResult : Microsoft.AspNetCore.Http.IResult + where TSchema : GraphQL.Types.ISchema + { + public GraphQLExecutionHttpResult(GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddlewareOptions options) { } + public GraphQLExecutionHttpResult(System.Action? configure = null) { } + public virtual System.Threading.Tasks.Task ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext httpContext) { } + } + public class GraphQLHttpMiddleware : GraphQL.Server.Transports.AspNetCore.IUserContextBuilder + { + public GraphQLHttpMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.IGraphQLTextSerializer serializer, GraphQL.IDocumentExecuter documentExecuter, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddlewareOptions options, Microsoft.Extensions.Hosting.IHostApplicationLifetime hostApplicationLifetime) { } + protected virtual System.Collections.Generic.IEnumerable SupportedWebSocketSubProtocols { get; } + protected virtual System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload) { } + protected virtual GraphQL.Server.Transports.AspNetCore.WebSockets.IOperationMessageProcessor CreateMessageProcessor(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection webSocketConnection, string subProtocol) { } + protected virtual GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection CreateWebSocketConnection(Microsoft.AspNetCore.Http.HttpContext httpContext, System.Net.WebSockets.WebSocket webSocket, System.Threading.CancellationToken cancellationToken) { } + protected virtual System.Threading.Tasks.Task ExecuteRequestAsync(Microsoft.AspNetCore.Http.HttpContext context, GraphQL.Transport.GraphQLRequest? request, System.IServiceProvider serviceProvider, System.Collections.Generic.IDictionary? userContext) { } + protected virtual System.Threading.Tasks.Task ExecuteScopedRequestAsync(Microsoft.AspNetCore.Http.HttpContext context, GraphQL.Transport.GraphQLRequest? request, System.Collections.Generic.IDictionary? userContext) { } + protected virtual System.Threading.Tasks.ValueTask HandleAuthorizeAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.ValueTask HandleAuthorizeWebSocketConnectionAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.Task HandleBatchRequestAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next, System.Collections.Generic.IList gqlRequests) { } + protected virtual System.Threading.Tasks.Task HandleBatchedRequestsNotSupportedAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.Task HandleContentTypeCouldNotBeParsedErrorAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.ValueTask HandleCsrfProtectionAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.Task HandleCsrfProtectionErrorAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.ValueTask HandleDeserializationErrorAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next, System.Exception exception) { } + protected virtual System.Threading.Tasks.Task HandleInvalidContentTypeErrorAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.Task HandleInvalidHttpMethodErrorAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.Task HandleNotAuthenticatedAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.Task HandleNotAuthorizedPolicyAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.AspNetCore.Authorization.AuthorizationResult authorizationResult) { } + protected virtual System.Threading.Tasks.Task HandleNotAuthorizedRoleAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.Task HandleRequestAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.Transport.GraphQLRequest gqlRequest) { } + protected virtual System.Threading.Tasks.Task HandleWebSocketAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + protected virtual System.Threading.Tasks.Task HandleWebSocketSubProtocolNotSupportedAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + public virtual System.Threading.Tasks.Task InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context) { } + protected virtual System.Threading.Tasks.Task InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) { } + [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { + "SingleRequest", + "BatchRequest"})] + protected virtual System.Threading.Tasks.Task?>?> ReadPostContentAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next, string? mediaType, System.Text.Encoding? sourceEncoding) { } + protected virtual Microsoft.Net.Http.Headers.MediaTypeHeaderValue SelectResponseContentType(Microsoft.AspNetCore.Http.HttpContext context) { } + protected virtual System.Threading.Tasks.Task WriteErrorResponseAsync(Microsoft.AspNetCore.Http.HttpContext context, GraphQL.ExecutionError executionError) { } + protected virtual System.Threading.Tasks.Task WriteErrorResponseAsync(Microsoft.AspNetCore.Http.HttpContext context, System.Net.HttpStatusCode httpStatusCode, GraphQL.ExecutionError executionError) { } + protected virtual System.Threading.Tasks.Task WriteErrorResponseAsync(Microsoft.AspNetCore.Http.HttpContext context, System.Net.HttpStatusCode httpStatusCode, string errorMessage) { } + protected virtual System.Threading.Tasks.Task WriteJsonResponseAsync(Microsoft.AspNetCore.Http.HttpContext context, GraphQL.ExecutionResult result) { } + protected virtual System.Threading.Tasks.Task WriteJsonResponseAsync(Microsoft.AspNetCore.Http.HttpContext context, System.Net.HttpStatusCode httpStatusCode, TResult result) { } + } + public class GraphQLHttpMiddlewareOptions : GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions + { + public GraphQLHttpMiddlewareOptions() { } + public System.Collections.Generic.List AuthenticationSchemes { get; set; } + public bool AuthorizationRequired { get; set; } + public string? AuthorizedPolicy { get; set; } + public System.Collections.Generic.List AuthorizedRoles { get; set; } + public bool CsrfProtectionEnabled { get; set; } + public System.Collections.Generic.List CsrfProtectionHeaders { get; set; } + public Microsoft.Net.Http.Headers.MediaTypeHeaderValue DefaultResponseContentType { get; set; } + public bool EnableBatchedRequests { get; set; } + public bool ExecuteBatchedRequestsInParallel { get; set; } + public bool HandleGet { get; set; } + public bool HandlePost { get; set; } + public bool HandleWebSockets { get; set; } + public int? MaximumFileCount { get; set; } + public long? MaximumFileSize { get; set; } + public bool ReadExtensionsFromQueryString { get; set; } + public bool ReadFormOnPost { get; set; } + public bool ReadQueryStringOnPost { get; set; } + public bool ReadVariablesFromQueryString { get; set; } + public bool? ValidationErrorsReturnBadRequest { get; set; } + public GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions WebSockets { get; set; } + } + public class GraphQLHttpMiddleware : GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddleware + where TSchema : GraphQL.Types.ISchema + { + public GraphQLHttpMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, GraphQL.IGraphQLTextSerializer serializer, GraphQL.IDocumentExecuter documentExecuter, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddlewareOptions options, Microsoft.Extensions.Hosting.IHostApplicationLifetime hostApplicationLifetime) { } + protected override System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload) { } + } + public sealed class HttpGetValidationRule : GraphQL.Validation.ValidationRuleBase + { + public HttpGetValidationRule() { } + public override System.Threading.Tasks.ValueTask GetPreNodeVisitorAsync(GraphQL.Validation.ValidationContext context) { } + } + public sealed class HttpPostValidationRule : GraphQL.Validation.ValidationRuleBase + { + public HttpPostValidationRule() { } + public override System.Threading.Tasks.ValueTask GetPreNodeVisitorAsync(GraphQL.Validation.ValidationContext context) { } + } + public interface IAuthorizationOptions + { + bool AuthorizationRequired { get; } + string? AuthorizedPolicy { get; } + System.Collections.Generic.IEnumerable AuthorizedRoles { get; } + } + public interface IUserContextBuilder + { + System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload); + } + public interface IUserContextBuilder : GraphQL.Server.Transports.AspNetCore.IUserContextBuilder + where TSchema : GraphQL.Types.ISchema { } + public class MediaTypeAttribute : GraphQL.GraphQLAttribute + { + public MediaTypeAttribute(params string[] mimeTypes) { } + public override void Modify(GraphQL.Types.QueryArgument queryArgument) { } + public override void Modify(GraphQL.Types.FieldType fieldType, bool isInputType) { } + } + public class UserContextBuilder : GraphQL.Server.Transports.AspNetCore.IUserContextBuilder + where TUserContext : System.Collections.Generic.IDictionary + { + public UserContextBuilder(System.Func> func) { } + public UserContextBuilder(System.Func func) { } + public UserContextBuilder(System.Func> func) { } + public UserContextBuilder(System.Func func) { } + public System.Threading.Tasks.ValueTask?> BuildUserContextAsync(Microsoft.AspNetCore.Http.HttpContext context, object? payload) { } + } +} +namespace GraphQL.Server.Transports.AspNetCore.Errors +{ + public class AccessDeniedError : GraphQL.Validation.ValidationError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode + { + public AccessDeniedError(string resource) { } + public AccessDeniedError(string resource, GraphQLParser.ROM originalQuery, params GraphQLParser.AST.ASTNode[] nodes) { } + public Microsoft.AspNetCore.Authorization.AuthorizationResult? PolicyAuthorizationResult { get; set; } + public string? PolicyRequired { get; set; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } + public System.Collections.Generic.List? RolesRequired { get; set; } + } + public class BatchedRequestsNotSupportedError : GraphQL.Execution.RequestError + { + public BatchedRequestsNotSupportedError() { } + } + public class CsrfProtectionError : GraphQL.Execution.RequestError + { + public CsrfProtectionError(System.Collections.Generic.IEnumerable headersRequired) { } + public CsrfProtectionError(System.Collections.Generic.IEnumerable headersRequired, System.Exception innerException) { } + } + public class FileCountExceededError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode + { + public FileCountExceededError() { } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } + } + public class FileSizeExceededError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode + { + public FileSizeExceededError() { } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } + } + public class HttpMethodValidationError : GraphQL.Validation.ValidationError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode + { + public HttpMethodValidationError(GraphQLParser.ROM originalQuery, GraphQLParser.AST.ASTNode node, string message) { } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } + } + public interface IHasPreferredStatusCode + { + System.Net.HttpStatusCode PreferredStatusCode { get; } + } + public class InvalidContentTypeError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode + { + public InvalidContentTypeError() { } + public InvalidContentTypeError(string message) { } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } + } + public class InvalidMapError : GraphQL.Execution.RequestError + { + public InvalidMapError(string message, System.Exception? innerException = null) { } + } + public class JsonInvalidError : GraphQL.Execution.RequestError + { + public JsonInvalidError() { } + public JsonInvalidError(System.Exception innerException) { } + } + public class WebSocketSubProtocolNotSupportedError : GraphQL.Execution.RequestError + { + public WebSocketSubProtocolNotSupportedError(System.Collections.Generic.IEnumerable requestedSubProtocols) { } + } +} +namespace GraphQL.Server.Transports.AspNetCore.WebSockets +{ + public abstract class BaseSubscriptionServer : GraphQL.Server.Transports.AspNetCore.WebSockets.IOperationMessageProcessor, System.IDisposable + { + protected BaseSubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions) { } + protected System.Threading.CancellationToken CancellationToken { get; } + protected GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection Connection { get; } + protected virtual System.TimeSpan DefaultConnectionTimeout { get; } + protected virtual System.TimeSpan DefaultKeepAliveTimeout { get; } + protected bool Initialized { get; } + protected GraphQL.Server.Transports.AspNetCore.WebSockets.SubscriptionList Subscriptions { get; } + protected virtual System.Threading.Tasks.ValueTask AuthorizeAsync(GraphQL.Transport.OperationMessage message) { } + public virtual void Dispose() { } + protected virtual System.Threading.Tasks.Task ErrorAccessDeniedAsync() { } + protected virtual System.Threading.Tasks.Task ErrorConnectionInitializationTimeoutAsync() { } + protected virtual System.Threading.Tasks.Task ErrorIdAlreadyExistsAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task ErrorIdCannotBeBlankAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task ErrorNotInitializedAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task ErrorTooManyInitializationRequestsAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task ErrorUnrecognizedMessageAsync(GraphQL.Transport.OperationMessage message) { } + protected abstract System.Threading.Tasks.Task ExecuteRequestAsync(GraphQL.Transport.OperationMessage message); + protected virtual System.Threading.Tasks.Task HandleErrorDuringSubscribeAsync(GraphQL.Transport.OperationMessage message, System.Exception ex) { } + protected virtual System.Threading.Tasks.Task HandleErrorFromSourceAsync(System.Exception exception) { } + public virtual System.Threading.Tasks.Task InitializeConnectionAsync() { } + protected virtual System.Threading.Tasks.Task OnCloseConnectionAsync() { } + protected abstract System.Threading.Tasks.Task OnConnectionAcknowledgeAsync(GraphQL.Transport.OperationMessage message); + protected virtual System.Threading.Tasks.Task OnConnectionInitAsync(GraphQL.Transport.OperationMessage message) { } + [System.Obsolete("Please use the OnConnectionInitAsync(message) and OnKeepAliveLoopAsync methods in" + + "stead. This method will be removed in a future version of this library.")] + protected virtual System.Threading.Tasks.Task OnConnectionInitAsync(GraphQL.Transport.OperationMessage message, bool smartKeepAlive) { } + protected virtual System.Threading.Tasks.Task OnConnectionInitWaitTimeoutAsync() { } + protected virtual System.Threading.Tasks.Task OnKeepAliveLoopAsync() { } + protected virtual System.Threading.Tasks.Task OnKeepAliveLoopAsync(System.TimeSpan keepAliveTimeout, GraphQL.Server.Transports.AspNetCore.WebSockets.KeepAliveMode keepAliveMode) { } + public abstract System.Threading.Tasks.Task OnMessageReceivedAsync(GraphQL.Transport.OperationMessage message); + protected virtual System.Threading.Tasks.Task OnNotAuthenticatedAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task OnNotAuthorizedPolicyAsync(GraphQL.Transport.OperationMessage message, Microsoft.AspNetCore.Authorization.AuthorizationResult result) { } + protected virtual System.Threading.Tasks.Task OnNotAuthorizedRoleAsync(GraphQL.Transport.OperationMessage message) { } + protected abstract System.Threading.Tasks.Task OnSendKeepAliveAsync(); + protected abstract System.Threading.Tasks.Task SendCompletedAsync(string id); + protected abstract System.Threading.Tasks.Task SendDataAsync(string id, GraphQL.ExecutionResult result); + protected virtual System.Threading.Tasks.Task SendErrorResultAsync(GraphQL.Transport.OperationMessage message, GraphQL.ExecutionError executionError) { } + protected virtual System.Threading.Tasks.Task SendErrorResultAsync(GraphQL.Transport.OperationMessage message, GraphQL.ExecutionResult result) { } + protected virtual System.Threading.Tasks.Task SendErrorResultAsync(string id, GraphQL.ExecutionError executionError) { } + protected abstract System.Threading.Tasks.Task SendErrorResultAsync(string id, GraphQL.ExecutionResult result); + protected virtual System.Threading.Tasks.Task SendSingleResultAsync(GraphQL.Transport.OperationMessage message, GraphQL.ExecutionResult result) { } + protected virtual System.Threading.Tasks.Task SendSubscriptionSuccessfulAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task SubscribeAsync(GraphQL.Transport.OperationMessage message, bool overwrite) { } + protected bool TryInitialize() { } + protected virtual System.Threading.Tasks.Task UnsubscribeAsync(string? id) { } + } + public class GraphQLWebSocketOptions + { + public GraphQLWebSocketOptions() { } + public System.TimeSpan? ConnectionInitWaitTimeout { get; set; } + public bool DisconnectAfterAnyError { get; set; } + public bool DisconnectAfterErrorEvent { get; set; } + public System.TimeSpan? DisconnectionTimeout { get; set; } + public GraphQL.Server.Transports.AspNetCore.WebSockets.KeepAliveMode KeepAliveMode { get; set; } + public System.TimeSpan? KeepAliveTimeout { get; set; } + public System.Collections.Generic.List SupportedWebSocketSubProtocols { get; set; } + } + public interface IOperationMessageProcessor : System.IDisposable + { + System.Threading.Tasks.Task InitializeConnectionAsync(); + System.Threading.Tasks.Task OnMessageReceivedAsync(GraphQL.Transport.OperationMessage message); + } + public interface IWebSocketAuthenticationService + { + System.Threading.Tasks.Task AuthenticateAsync(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, string subProtocol, GraphQL.Transport.OperationMessage operationMessage); + } + public interface IWebSocketConnection : System.IDisposable + { + Microsoft.AspNetCore.Http.HttpContext HttpContext { get; } + System.DateTime LastMessageSentAt { get; } + System.Threading.CancellationToken RequestAborted { get; } + System.Threading.Tasks.Task CloseAsync(); + System.Threading.Tasks.Task CloseAsync(int eventId, string? description); + System.Threading.Tasks.Task ExecuteAsync(GraphQL.Server.Transports.AspNetCore.WebSockets.IOperationMessageProcessor operationMessageProcessor); + System.Threading.Tasks.Task SendMessageAsync(GraphQL.Transport.OperationMessage message); + } + public enum KeepAliveMode + { + Default = 0, + Timeout = 1, + Interval = 2, + TimeoutWithPayload = 3, + } + public sealed class SubscriptionList : System.IDisposable + { + public SubscriptionList() { } + public System.IDisposable this[string id] { set; } + public bool CompareExchange(string id, System.IDisposable oldSubscription, System.IDisposable newSubscription) { } + public bool Contains(string id) { } + public bool Contains(string id, System.IDisposable subscription) { } + public void Dispose() { } + public bool TryAdd(string id, System.IDisposable subscription) { } + public bool TryRemove(string id) { } + public bool TryRemove(string id, System.IDisposable oldSubscription) { } + } + public class WebSocketConnection : GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection, System.IDisposable + { + public WebSocketConnection(Microsoft.AspNetCore.Http.HttpContext httpContext, System.Net.WebSockets.WebSocket webSocket, GraphQL.IGraphQLSerializer serializer, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, System.Threading.CancellationToken requestAborted) { } + protected virtual System.TimeSpan DefaultDisconnectionTimeout { get; } + public Microsoft.AspNetCore.Http.HttpContext HttpContext { get; } + public System.DateTime LastMessageSentAt { get; } + public System.Threading.CancellationToken RequestAborted { get; } + protected int SendQueueCount { get; } + public System.Threading.Tasks.Task CloseAsync() { } + public System.Threading.Tasks.Task CloseAsync(int eventId, string? description) { } + public virtual void Dispose() { } + public virtual System.Threading.Tasks.Task ExecuteAsync(GraphQL.Server.Transports.AspNetCore.WebSockets.IOperationMessageProcessor operationMessageProcessor) { } + protected virtual System.Threading.Tasks.Task OnCloseOutputAsync(System.Net.WebSockets.WebSocketCloseStatus closeStatus, string? closeDescription) { } + protected virtual System.Threading.Tasks.Task OnDispatchMessageAsync(GraphQL.Server.Transports.AspNetCore.WebSockets.IOperationMessageProcessor operationMessageProcessor, GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task OnNonGracefulShutdownAsync(bool receivedCloseMessage, bool sentCloseMessage) { } + protected virtual System.Threading.Tasks.Task OnSendMessageAsync(GraphQL.Transport.OperationMessage message) { } + public virtual System.Threading.Tasks.Task SendMessageAsync(GraphQL.Transport.OperationMessage message) { } + } +} +namespace GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWs +{ + public static class MessageType + { + public const string Complete = "complete"; + public const string ConnectionAck = "connection_ack"; + public const string ConnectionInit = "connection_init"; + public const string Error = "error"; + public const string Next = "next"; + public const string Ping = "ping"; + public const string Pong = "pong"; + public const string Subscribe = "subscribe"; + } + public class PingPayload + { + public PingPayload() { } + public string? id { get; set; } + } + public class SubscriptionServer : GraphQL.Server.Transports.AspNetCore.WebSockets.BaseSubscriptionServer + { + public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null) { } + protected GraphQL.IDocumentExecuter DocumentExecuter { get; } + protected GraphQL.IGraphQLSerializer Serializer { get; } + protected Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ServiceScopeFactory { get; } + protected System.Collections.Generic.IDictionary? UserContext { get; set; } + protected GraphQL.Server.Transports.AspNetCore.IUserContextBuilder UserContextBuilder { get; } + public static string SubProtocol { get; } + protected override System.Threading.Tasks.ValueTask AuthorizeAsync(GraphQL.Transport.OperationMessage message) { } + protected override System.Threading.Tasks.Task ExecuteRequestAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task OnCompleteAsync(GraphQL.Transport.OperationMessage message) { } + protected override System.Threading.Tasks.Task OnConnectionAcknowledgeAsync(GraphQL.Transport.OperationMessage message) { } + [System.Obsolete("Please use the OnConnectionInitAsync and OnKeepAliveLoopAsync methods instead. Th" + + "is method will be removed in a future version of this library.")] + protected override System.Threading.Tasks.Task OnConnectionInitAsync(GraphQL.Transport.OperationMessage message, bool smartKeepAlive) { } + protected override System.Threading.Tasks.Task OnKeepAliveLoopAsync(System.TimeSpan keepAliveTimeout, GraphQL.Server.Transports.AspNetCore.WebSockets.KeepAliveMode keepAliveMode) { } + public override System.Threading.Tasks.Task OnMessageReceivedAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task OnPingAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task OnPongAsync(GraphQL.Transport.OperationMessage message) { } + protected override System.Threading.Tasks.Task OnSendKeepAliveAsync() { } + protected virtual System.Threading.Tasks.Task OnSubscribeAsync(GraphQL.Transport.OperationMessage message) { } + protected override System.Threading.Tasks.Task SendCompletedAsync(string id) { } + protected override System.Threading.Tasks.Task SendDataAsync(string id, GraphQL.ExecutionResult result) { } + protected override System.Threading.Tasks.Task SendErrorResultAsync(string id, GraphQL.ExecutionResult result) { } + } +} +namespace GraphQL.Server.Transports.AspNetCore.WebSockets.SubscriptionsTransportWs +{ + public static class MessageType + { + public const string GQL_COMPLETE = "complete"; + public const string GQL_CONNECTION_ACK = "connection_ack"; + public const string GQL_CONNECTION_ERROR = "connection_error"; + public const string GQL_CONNECTION_INIT = "connection_init"; + public const string GQL_CONNECTION_KEEP_ALIVE = "ka"; + public const string GQL_CONNECTION_TERMINATE = "connection_terminate"; + public const string GQL_DATA = "data"; + public const string GQL_ERROR = "error"; + public const string GQL_START = "start"; + public const string GQL_STOP = "stop"; + } + public class SubscriptionServer : GraphQL.Server.Transports.AspNetCore.WebSockets.BaseSubscriptionServer + { + public SubscriptionServer(GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketConnection connection, GraphQL.Server.Transports.AspNetCore.WebSockets.GraphQLWebSocketOptions options, GraphQL.Server.Transports.AspNetCore.IAuthorizationOptions authorizationOptions, GraphQL.IDocumentExecuter executer, GraphQL.IGraphQLSerializer serializer, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory, GraphQL.Server.Transports.AspNetCore.IUserContextBuilder userContextBuilder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService? authenticationService = null) { } + protected GraphQL.IDocumentExecuter DocumentExecuter { get; } + protected GraphQL.IGraphQLSerializer Serializer { get; } + protected Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ServiceScopeFactory { get; } + protected System.Collections.Generic.IDictionary? UserContext { get; set; } + protected GraphQL.Server.Transports.AspNetCore.IUserContextBuilder UserContextBuilder { get; } + public static string SubProtocol { get; } + protected override System.Threading.Tasks.ValueTask AuthorizeAsync(GraphQL.Transport.OperationMessage message) { } + protected override System.Threading.Tasks.Task ErrorAccessDeniedAsync() { } + protected override System.Threading.Tasks.Task ExecuteRequestAsync(GraphQL.Transport.OperationMessage message) { } + protected override System.Threading.Tasks.Task OnConnectionAcknowledgeAsync(GraphQL.Transport.OperationMessage message) { } + [System.Obsolete("Please use the OnConnectionInitAsync and OnKeepAliveLoopAsync methods instead. Th" + + "is method will be removed in a future version of this library.")] + protected override System.Threading.Tasks.Task OnConnectionInitAsync(GraphQL.Transport.OperationMessage message, bool smartKeepAlive) { } + protected override System.Threading.Tasks.Task OnKeepAliveLoopAsync(System.TimeSpan keepAliveTimeout, GraphQL.Server.Transports.AspNetCore.WebSockets.KeepAliveMode keepAliveMode) { } + public override System.Threading.Tasks.Task OnMessageReceivedAsync(GraphQL.Transport.OperationMessage message) { } + protected override System.Threading.Tasks.Task OnSendKeepAliveAsync() { } + protected virtual System.Threading.Tasks.Task OnStartAsync(GraphQL.Transport.OperationMessage message) { } + protected virtual System.Threading.Tasks.Task OnStopAsync(GraphQL.Transport.OperationMessage message) { } + protected override System.Threading.Tasks.Task SendCompletedAsync(string id) { } + protected override System.Threading.Tasks.Task SendDataAsync(string id, GraphQL.ExecutionResult result) { } + protected override System.Threading.Tasks.Task SendErrorResultAsync(string id, GraphQL.ExecutionResult result) { } + } +} +namespace GraphQL +{ + public static class ServerGraphQLBuilderExtensions + { + public static GraphQL.DI.IGraphQLBuilder AddAuthorizationRule(this GraphQL.DI.IGraphQLBuilder builder) { } + public static GraphQL.DI.IGraphQLBuilder AddFormFileGraphType(this GraphQL.DI.IGraphQLBuilder builder) { } + public static GraphQL.DI.IGraphQLBuilder AddUserContextBuilder<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TUserContextBuilder>(this GraphQL.DI.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 { } + 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 { } + public static GraphQL.DI.IGraphQLBuilder AddWebSocketAuthentication(this GraphQL.DI.IGraphQLBuilder builder, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService webSocketAuthenticationService) { } + public static GraphQL.DI.IGraphQLBuilder AddWebSocketAuthentication(this GraphQL.DI.IGraphQLBuilder builder, System.Func factory) { } + public static GraphQL.DI.IGraphQLBuilder AddWebSocketAuthentication<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TWebSocketAuthenticationService>(this GraphQL.DI.IGraphQLBuilder builder) + where TWebSocketAuthenticationService : class, GraphQL.Server.Transports.AspNetCore.WebSockets.IWebSocketAuthenticationService { } + } +} +namespace GraphQL.Utilities.Federation +{ + public static class GraphQLHttpRequestExtensions + { + public static bool IsApolloFederatedTracingEnabled(this Microsoft.AspNetCore.Http.HttpRequest request) { } + } +} +namespace Microsoft.AspNetCore.Builder +{ + public class GraphQLEndpointConventionBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder + { + public void Add(System.Action convention) { } + } + public static class GraphQLHttpApplicationBuilderExtensions + { + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQL(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder, Microsoft.AspNetCore.Http.PathString path, System.Action? configureMiddleware = null) { } + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQL(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder, string path = "/graphql", System.Action? configureMiddleware = null) { } + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQL(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder, Microsoft.AspNetCore.Http.PathString path, System.Action? configureMiddleware = null) + where TSchema : GraphQL.Types.ISchema { } + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQL<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)] TMiddleware>(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder, Microsoft.AspNetCore.Http.PathString path, params object[] args) + where TMiddleware : GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddleware { } + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQL(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder, string path = "/graphql", System.Action? configureMiddleware = null) + where TSchema : GraphQL.Types.ISchema { } + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseGraphQL<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)] TMiddleware>(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder, string path = "/graphql", params object[] args) + where TMiddleware : GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddleware { } + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseIgnoreDisconnections(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder) { } + } + public static class GraphQLHttpEndpointRouteBuilderExtensions + { + public static Microsoft.AspNetCore.Builder.GraphQLEndpointConventionBuilder MapGraphQL(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern = "graphql", System.Action? configureMiddleware = null) { } + public static Microsoft.AspNetCore.Builder.GraphQLEndpointConventionBuilder MapGraphQL(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern = "graphql", System.Action? configureMiddleware = null) + where TSchema : GraphQL.Types.ISchema { } + public static Microsoft.AspNetCore.Builder.GraphQLEndpointConventionBuilder MapGraphQL<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)] TMiddleware>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern = "graphql", params object[] args) + where TMiddleware : GraphQL.Server.Transports.AspNetCore.GraphQLHttpMiddleware { } + } +} \ No newline at end of file diff --git a/tests/Samples.HttpResult.Tests/EndToEndTests.cs b/tests/Samples.HttpResult.Tests/EndToEndTests.cs new file mode 100644 index 00000000..6332ad0f --- /dev/null +++ b/tests/Samples.HttpResult.Tests/EndToEndTests.cs @@ -0,0 +1,26 @@ +using Samples.Tests; + +namespace Samples.Controller.Tests; + +public class EndToEndTests +{ + [Fact] + public Task GraphiQL() + => new ServerTests().VerifyGraphiQLAsync(); + + [Fact] + public Task GraphQLGet() + => new ServerTests().VerifyGraphQLGetAsync("/graphql"); + + [Fact] + public Task GraphQLPost() + => new ServerTests().VerifyGraphQLPostAsync("/graphql"); + + [Fact] + public Task GraphQLWebSocket() + => new ServerTests().VerifyGraphQLWebSocketsAsync("/graphql"); + + [Fact] + public Task GraphQLResultPost() + => new ServerTests().VerifyGraphQLPostAsync("/graphql-result"); +} diff --git a/tests/Samples.HttpResult.Tests/Samples.HttpResult.Tests.csproj b/tests/Samples.HttpResult.Tests/Samples.HttpResult.Tests.csproj new file mode 100644 index 00000000..f8f513da --- /dev/null +++ b/tests/Samples.HttpResult.Tests/Samples.HttpResult.Tests.csproj @@ -0,0 +1,14 @@ + + + + + net8.0 + End to end tests for the Samples.HttpResult project + + + + + + + + diff --git a/tests/Transports.AspNetCore.Tests/ExecutionResultHttpResultTests.cs b/tests/Transports.AspNetCore.Tests/ExecutionResultHttpResultTests.cs new file mode 100644 index 00000000..eb8bd58a --- /dev/null +++ b/tests/Transports.AspNetCore.Tests/ExecutionResultHttpResultTests.cs @@ -0,0 +1,96 @@ +#if NET6_0_OR_GREATER +using System.Globalization; + +namespace Tests; + +public class ExecutionResultHttpResultTests : IDisposable +{ + private readonly TestServer _server; + + public ExecutionResultHttpResultTests() + { + var _hostBuilder = new WebHostBuilder(); + _hostBuilder.ConfigureServices(services => + { + services.AddSingleton(); + services.AddGraphQL(b => b + .AddAutoSchema() + .AddSystemTextJson()); + services.AddRouting(); + }); + _hostBuilder.Configure(app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/graphql", async (HttpContext context, IDocumentExecuter documentExecuter) => + { + var query = context.Request.Query["query"].ToString(); + var resultCodeStr = context.Request.Query["resultCode"].ToString(); + var jsonType = context.Request.Query["jsonType"].ToString() == "true"; + + var result = await documentExecuter.ExecuteAsync(new() + { + Query = query, + RequestServices = context.RequestServices, + CancellationToken = context.RequestAborted, + }); + + ExecutionResultHttpResult result2; + if (string.IsNullOrEmpty(resultCodeStr)) + result2 = new ExecutionResultHttpResult(result); + else + result2 = new ExecutionResultHttpResult(result, (System.Net.HttpStatusCode)int.Parse(resultCodeStr, CultureInfo.InvariantCulture)); + + if (jsonType) + result2.ContentType = "application/json"; + + return result2; + }); + }); + }); + _server = new TestServer(_hostBuilder); + } + + [Fact] + public async Task Basic() + { + var str = await _server.ExecuteGet("/graphql?query={count}"); + str.ShouldBe("{\"data\":{\"count\":0}}"); + } + + [Fact] + public async Task ForcedResultCode() + { + var str2 = await _server.ExecuteGet("/graphql?query={}&resultCode=200"); + str2.ShouldBe("""{"errors":[{"message":"Error parsing query: Expected Name, found }; for more information see http://spec.graphql.org/October2021/#Field","locations":[{"line":1,"column":2}],"extensions":{"code":"SYNTAX_ERROR","codes":["SYNTAX_ERROR"]}}]}"""); + } + + [Fact] + public async Task AltContentType() + { + using var httpClient = _server.CreateClient(); + using var request = new HttpRequestMessage(HttpMethod.Get, "/graphql?query={count}&jsonType=true"); + var response = await httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + var contentType = response.Content.Headers.ContentType; + contentType.ShouldNotBeNull(); + contentType.MediaType.ShouldBe("application/json"); + var str3 = await response.Content.ReadAsStringAsync(); + str3.ShouldBe("{\"data\":{\"count\":0}}"); + } + + [Fact] + public async Task ReturnsBadRequestForUnexecutedResults() + { + using var httpClient = _server.CreateClient(); + using var request = new HttpRequestMessage(HttpMethod.Get, "/graphql?query={}"); + var response = await httpClient.SendAsync(request); + response.StatusCode.ShouldBe(System.Net.HttpStatusCode.BadRequest); + var str3 = await response.Content.ReadAsStringAsync(); + str3.ShouldBe("""{"errors":[{"message":"Error parsing query: Expected Name, found }; for more information see http://spec.graphql.org/October2021/#Field","locations":[{"line":1,"column":2}],"extensions":{"code":"SYNTAX_ERROR","codes":["SYNTAX_ERROR"]}}]}"""); + } + + public void Dispose() => _server.Dispose(); +} +#endif