Hot Chocolate comes with integration to the ASP.NET Core endpoints API. The middleware implementation follows the current GraphQL over HTTP Spec.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){ app.UseRouting()
app.UseEndpoints(endpoints => { endpoints.MapGraphQL(); });}
GraphQL over HTTP Spec
The following GraphQL requests follow the current GraphQL over HTTP spec draft.
If no path is specified, the GraphQL middleware will follow the spec recommendation to map the endpoint to /graphql
.
http://example.com/graphql
http://product.example.com/graphql
http://example.com/product/graphql
GraphQL HTTP POST requests
The GraphQL HTTP POST request is the most commonly used variant for GraphQL requests over HTTP and is specified here.
request:
POST /graphqlHOST: foo.exampleContent-Type: application/json
{ "query": "query($id: ID!){user(id:$id){name}}", "variables": { "id": "QVBJcy5ndXJ1" }}
response:
HTTP/1.1 200 OKContent-Type: application/json
{ "data": { "user": { "name": "Jon Doe" } }}
GraphQL HTTP GET request
GraphQL can also be served through an HTTP GET request. You have the same options as the HTTP POST request, just that the request properties are provided as query parameters. GraphQL HTTP GET requests can be a good choice if you are looking to cache GraphQL requests.
For example, if we wanted to execute the following GraphQL query:
query($id: ID!) { user(id: $id) { name }}
With the following query variables:
{ "id": "QVBJcy5ndXJ1"}
This request could be sent via an HTTP GET as follows:
request:
GET /graphql?query=query(%24id%3A%20ID!)%7Buser(id%3A%24id)%7Bname%7D%7D&variables=%7B%22id%22%3A%22QVBJcy5ndXJ1%22%7D`HOST: foo.example
response:
HTTP/1.1 200 OKContent-Type: application/json
{ "data": { "user": { "name": "Jon Doe" } }}
Note: {query} and {operationName} parameters are encoded as raw strings in the query component. Therefore if the query string contained operationName=null then it should be interpreted as the {operationName} being the string "null". If a literal null is desired, the parameter (e.g. {operationName}) should be omitted.
The GraphQL HTTP GET request is specified here.
By default, Hot Chocolate will only serve query operations when HTTP GET requests are used. You can change this default by specifying the GraphQL server options.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){ app.UseRouting()
app.UseEndpoints(endpoints => { endpoints .MapGraphQL() .WithOptions(new GraphQLServerOptions { AllowedGetOperations = AllowedGetOperations.QueryAndMutation }); });}
You can also entirely deactivate HTTP GET request handling.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){ app.UseRouting()
app.UseEndpoints(endpoints => { endpoints .MapGraphQL() .WithOptions(new GraphQLServerOptions { EnableGetRequests = false }); });}
Incremental Delivery over HTTP
The Hot Chocolate GraphQL server supports incremental delivery over HTTP, which essentially uses HTTP chunked transfer encoding combined with the specification of multipart content defined by the W3 in rfc1341.
The incremental delivery is at the moment at the RFC stage and is specified here.
Incremental delivery is used with @defer
, @stream
, and with request batching.
Additional Requests
Apart from the requests defined by the GraphQL over HTTP spec, Hot Chocolate allows you to batch requests, download the GraphQL SDL, and many more things.
Many of the request types stated in this section are on their way into the GraphQL over HTTP spec, and we will update this document as the spec, and its RFCs change.
GraphQL Schema request
Although you can access and query the schema definition through introspection, we support fetching the GraphQL schema SDL as a file. The GraphQL schema SDL is richer with more information and easier to read.
request:
GET /graphql?sdlHOST: foo.example
response:
HTTP/1.1 200 OKContent-Type: application/graphql
type Query { hello: String!}
GraphQL HTTP POST batching request
We support two kinds of batching variants.
The first variant to batch GraphQL requests is by sending in an array of GraphQL requests. Hot Chocolate will execute them in order.
POST /graphqlHOST: foo.exampleContent-Type: application/json
[ { # The query document. "query": "query getHero { hero { name } }",
# The name of the operation that shall be executed. "operationName": "getHero",
# A key under which a query document was saved on the server. "id": "W5vrrAIypCbniaIYeroNnw==",
# The variable values for this request. "variables": { "a": 1, "b": "abc" },
# Custom properties that can be passed to the execution engine context data. "extensions": { "a": 1, "b": "abc" } }, { # The query document. "query": "query getHero { hero { name } }",
# The name of the operation that shall be executed. "operationName": "getHero",
# A key under which a query document was saved on the server. "id": "W5vrrAIypCbniaIYeroNnw==",
# The variable values for this request. "variables": { "a": 1, "b": "abc" },
# Custom properties that can be passed to the execution engine context data. "extensions": { "a": 1, "b": "abc" } },]
The second GraphQL batching variant is called operation batching, where you send in one GraphQL request document with multiple operations. The operation execution order is then specified as a query param.
POST /graphql?batchOperations=[a,b]HOST: foo.exampleContent-Type: application/json
{ # The query document. "query": "query a { hero { name } } query b { hero { name } }",
# The name of the operation that shall be executed. "operationName": "getHero",
# A key under which a query document was saved on the server. "id": "W5vrrAIypCbniaIYeroNnw==",
# The variable values for this request. "variables": { "a": 1, "b": "abc" },
# Custom properties that can be passed to the execution engine context data. "extensions": { "a": 1, "b": "abc" }}
By default, the GraphQL server will use the incremental delivery over HTTP specification to write the stream results as soon as they are available. This means that depending on your client implementation; you can start using the results as they appear in order.
The serialization defaults can be changed like the following:
services.AddHttpResultSerializer( batchSerialization: HttpResultSerialization.JsonArray, deferSerialization: HttpResultSerialization.MultiPartChunked)
More about batching can be found here.
GraphQL multipart request specification
Hot Chocolate implements the GraphQL multipart request specification which allows for file upload streams in your browser. The GraphQL multipart request specification can be found here.
In order to use file upload streams in your input types or as an argument register the Upload
scalar like the following.
service .AddGraphQLServer() ... .AddType<UploadType>();
In your resolver or input type you can then use the IFile
interface to use the upload scalar.
public class Mutation{ public async Task<bool> UploadFileAsync(IFile file) { using Stream stream = file.OpenReadStream(); // you can now work with standard stream functionality of .NET to handle the file. }}
Note, that the
Upload
scalar can only be used as an input type and does not work on output types.
If you need to upload large files or set custom upload size limits, you can configure those by registering custom FormOptions
.
services.Configure<FormOptions>(options =>{ // Set the limit to 256 MB options.MultipartBodyLengthLimit = 268435456;});
Based on your WebServer you might need to configure these limits elsewhere as well. Kestrel and IIS are covered in the ASP.NET Core Documentation.
Subscription Transport
Subscriptions are by default delivered over WebSocket. We have implemented the GraphQL over WebSocket Protocol specified by Apollo.
Alternative Transport Protocols
With version 11.1, we will add alternative transport protocols like the new proposal for the GraphQL over HTTP spec.
Moreover, we are working on allowing this protocol to be used over SignalR, which gives more flexibility to use subscriptions.
Tooling
The Hot Chocolate GraphQL server comes right out of the gate with excellent tooling. By default, we are mapping our GraphQL IDE Banana Cake Pop to the GraphQL endpoint. This means you just need to open your browser and navigate to the configured endpoint to send requests to your server, explore your schema, or build-up tests.
The GraphQL IDE can be disabled by specifying tool options:
endpoints .MapGraphQL() .WithOptions( new GraphQLServerOptions { Tool = { Enable = false } }));
Serialization
The Hot Chocolate GraphQL server has abstracted the result serialization with the IHttpResultSerializer
interface. The server uses the registered implementation to resolve the HTTP status code, the HTTP content type, and the serialized response from a GraphQL execution result.
/// <summary>/// This interface specifies how a GraphQL result is serialized to a HTTP response./// </summary>public interface IHttpResultSerializer{ /// <summary> /// Gets the HTTP content type for the specified execution result. /// </summary> /// <param name="result"> /// The GraphQL execution result. /// </param> /// <returns> /// Returns a string representing the content type, /// eg. "application/json; charset=utf-8". /// </returns> string GetContentType(IExecutionResult result);
/// <summary> /// Gets the HTTP status code for the specified execution result. /// </summary> /// <param name="result"> /// The GraphQL execution result. /// </param> /// <returns> /// Returns the HTTP status code, eg. <see cref="HttpStatusCode.OK"/>. /// </returns> HttpStatusCode GetStatusCode(IExecutionResult result);
/// <summary> /// Serializes the specified execution result. /// </summary> /// <param name="result"> /// The GraphQL execution result. /// </param> /// <param name="stream"> /// The HTTP response stream. /// </param> /// <param name="cancellationToken"> /// The request cancellation token. /// </param> ValueTask SerializeAsync( IExecutionResult result, Stream stream, CancellationToken cancellationToken);}
We have a default implementation (DefaultHttpResultSerializer
) that can be used to built custom logic on top of the original implementation to make extensibility easier. By default, we are using System.Text.Json
to serialize GraphQL execution results to JSON.
A custom implementation of the result serializer is registered like the following:
services.AddHttpResultSerializer<MyCustomHttpResultSerializer>();
If you, for instance, wanted to add some special error code handling when some error happened during execution, you could implement this like the following:
public class MyCustomHttpResultSerializer : DefaultHttpResultSerializer{ public override HttpStatusCode GetStatusCode(IExecutionResult result) { if (result is IQueryResult queryResult && queryResult.Errors?.Count > 0 && queryResult.Errors.Any(error => error.Code == "SOME_AUTH_ISSUE")) { return HttpStatusCode.Forbidden; }
return base.GetStatusCode(result); }}
GraphQL request customization
The GraphQL server allows you to customize how the GraphQL request is created. For this, you need to implement the IHttpRequestInterceptor
. For convenience reasons, we provide a default implementation (DefaultHttpRequestInterceptor
) that can be extended.
public class DefaultHttpRequestInterceptor : IHttpRequestInterceptor{ public virtual ValueTask OnCreateAsync( HttpContext context, IRequestExecutor requestExecutor, IQueryRequestBuilder requestBuilder, CancellationToken cancellationToken) { requestBuilder.TrySetServices(context.RequestServices); requestBuilder.TryAddProperty(nameof(HttpContext), context); requestBuilder.TryAddProperty(nameof(ClaimsPrincipal), context.User); requestBuilder.TryAddProperty(nameof(CancellationToken), context.RequestAborted);
if (context.IsTracingEnabled()) { requestBuilder.TryAddProperty(WellKnownContextData.EnableTracing, true); }
return default; }}
Suppose you want to add more data to a GraphQL request; override the OnCreateAsync
method, and add your custom data as a request property. These request properties are mapped to the request context data, which can be accessed in the field resolver or a field middleware through the context
.
if(context.ContextData.ContainsKey(nameof(HttpContext))){ // some logic}
The context data can also be injected into resolver methods.
public string MyResolver([GlobalState(nameof(HttpContext))] HttpContext context){ // some logic}
A good practice is to inherit from the GlobalStateAttribute
to create a custom typed attribute.
public string MyResolver([HttpContext] HttpContext context){ // some logic}
A custom http request interceptor can be registered like the following:
services.AddSocketSessionInterceptor<MyCustomHttpRequestInterceptor>();
Request Errors
The interceptor can be used to do general request validation. This essentially allows to fail the request before the GraphQL context is created. In order to create a GraphQL error response simply throw a GraphQLException
in the OnCreateAsync
method. The middleware will translate these to a proper GraphQL error response for the client. You also can customize the status code behavior by using the HTTP result serializer mentioned above.
Subscription session handling
The Hot Chocolate GraphQL server allows you to interact with the server's socket session handling by implementing ISocketSessionInterceptor
. For convenience reasons, we provide a default implementation (DefaultSocketSessionInterceptor
) that can be extended.
public class DefaultSocketSessionInterceptor : ISocketSessionInterceptor{ public virtual ValueTask<ConnectionStatus> OnConnectAsync( ISocketConnection connection, InitializeConnectionMessage message, CancellationToken cancellationToken) => new ValueTask<ConnectionStatus>(ConnectionStatus.Accept());
public virtual ValueTask OnRequestAsync( ISocketConnection connection, IQueryRequestBuilder requestBuilder, CancellationToken cancellationToken) { HttpContext context = connection.HttpContext; requestBuilder.TrySetServices(connection.RequestServices); requestBuilder.TryAddProperty(nameof(CancellationToken), connection.RequestAborted); requestBuilder.TryAddProperty(nameof(HttpContext), context); requestBuilder.TryAddProperty(nameof(ClaimsPrincipal), context.User);
if (connection.HttpContext.IsTracingEnabled()) { requestBuilder.TryAddProperty(WellKnownContextData.EnableTracing, true); }
return default; }
public virtual ValueTask OnCloseAsync( ISocketConnection connection, CancellationToken cancellationToken) => default;}
A custom socket session interceptor can be registered like the following:
services.AddSocketSessionInterceptor<MyCustomSocketSessionInterceptor>();