Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/simoncropp/graphql.attachments
Provides access to a HTTP stream in GraphQL
https://github.com/simoncropp/graphql.attachments
Last synced: 8 days ago
JSON representation
Provides access to a HTTP stream in GraphQL
- Host: GitHub
- URL: https://github.com/simoncropp/graphql.attachments
- Owner: SimonCropp
- License: mit
- Created: 2018-08-22T00:25:13.000Z (about 6 years ago)
- Default Branch: main
- Last Pushed: 2024-10-28T14:49:39.000Z (18 days ago)
- Last Synced: 2024-10-29T17:42:56.856Z (17 days ago)
- Language: C#
- Homepage:
- Size: 1020 KB
- Stars: 17
- Watchers: 2
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
- Funding: .github/FUNDING.yml
- License: license.txt
- Code of conduct: code_of_conduct.md
Awesome Lists containing this project
README
# GraphQL.Attachments
[![Build status](https://ci.appveyor.com/api/projects/status/wq5ox06crbl9c2py/branch/main?svg=true)](https://ci.appveyor.com/project/SimonCropp/graphql-attachments)
[![NuGet Status](https://img.shields.io/nuget/v/GraphQL.Attachments.svg)](https://www.nuget.org/packages/GraphQL.Attachments/)Provides access to a HTTP stream (via JavaScript on a web page) in [GraphQL](https://graphql-dotnet.github.io/) [Mutations](https://graphql-dotnet.github.io/docs/getting-started/mutations/) or [Queries](https://graphql-dotnet.github.io/docs/getting-started/queries). Attachments are transferred via a [multipart form](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition).
**See [Milestones](../../milestones?state=closed) for release notes.**
## NuGet package
https://nuget.org/packages/GraphQL.Attachments/
PM> Install-Package GraphQL.Attachments
## Usage in Graphs
Incoming and Outgoing attachments can be accessed via the `ResolveFieldContext`:
```cs
Field("withAttachment")
.Argument>("argument")
.Resolve(context =>
{
var incomingAttachments = context.IncomingAttachments();
var outgoingAttachments = context.OutgoingAttachments();foreach (var incoming in incomingAttachments.Values)
{
// For sample purpose echo the incoming request
// stream to the outgoing response stream
var memoryStream = new MemoryStream();
incoming.CopyTo(memoryStream);
memoryStream.Position = 0;
outgoingAttachments.AddStream(incoming.Name, memoryStream);
}return new Result
{
Argument = context.GetArgument("argument"),
};
});
```
snippet source | anchor## Server-side Middleware
### RequestReader instead of binding
When using Attachments the incoming request also requires the incoming form data to be parse. To facilitate this [RequestReader](/src/GraphQL.Attachments/RequestReader.cs) is used.:
```cs
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var cancel = context.RequestAborted;
var response = context.Response;
var request = context.Request;
var isGet = HttpMethods.IsGet(request.Method);
var isPost = HttpMethods.IsPost(request.Method);if (isGet)
{
var (query, inputs, operation) = readerWriter.ReadGet(request);
await Execute(response, query, operation, null, inputs, cancel);
return;
}if (isPost)
{
var (query, inputs, attachments, operation) = await readerWriter.ReadPost(request, cancel);
await Execute(response, query, operation, attachments, inputs, cancel);
return;
}response.Headers.Allow = "GET, POST";
response.StatusCode = (int) HttpStatusCode.BadRequest;
}
```
snippet source | anchor### Query Execution
To expose the attachments to the queries, the attachment context needs to be added to the `IDocumentExecuter`. This is done using `AttachmentsExtensions.ExecuteWithAttachments`:
```cs
var result = await executer.ExecuteWithAttachments(options, attachments);
```
snippet source | anchor### Result Writing
As with RequestReader for the incoming data, the outgoing data needs to be written with any resulting attachments. To facilitate this [ResponseWriter](/src/GraphQL.Attachments/ResponseWriter.cs) is used.
```cs
await readerWriter.WriteResult(response, result, cancel);
```
snippet source | anchor## Client - JavaScript
The JavaScript that submits the query does so through by building up a [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object and [POSTing](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data#The_POST_method) that via the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
#### Helper method for builgin post settings
```html
function BuildPostSettings() {
var data = new FormData();
var files = document.getElementById("files").files;
for (var i = 0; i < files.length; i++) {
data.append('files[]', files[i], files[i].name);
}
data.append(
"query",
'mutation{ withAttachment (argument: "argumentValue"){argument}}'
);return {
method: 'POST',
body: data
};
}
```
snippet source | anchor#### Post mutation and download result
```html
function PostMutationAndDownloadFile() {var postSettings = BuildPostSettings();
return fetch('graphql', postSettings)
.then(function (data) {
return data.formData().then(x => {
var resultContent = '';
x.forEach(e => {
// This is the attachments
if (e.name) {
var a = document.createElement('a');
var blob = new Blob([e]);
a.href = window.URL.createObjectURL(blob);
a.download = e.name;
a.click();
}
else {
resultContent += JSON.stringify(e);
}
});
result.innerHTML = resultContent;
});
});
}
```
snippet source | anchor#### Post mutation and display text result
```html
function PostMutationWithTextResult() {
var postSettings = BuildPostSettings();
return fetch('graphql', postSettings)
.then(function (data) {
return data.text().then(x => {
result.innerHTML = x;
});
});
}
```
snippet source | anchor## Client - .NET
Creating and posting a multipart form can be done using a combination of [MultipartFormDataContent](https://msdn.microsoft.com/en-us/library/system.net.http.multipartformdatacontent.aspx) and [HttpClient.PostAsync](https://msdn.microsoft.com/en-us/library/system.net.http.httpclient.postasync.aspx). To simplify this action the `ClientQueryExecutor` class can be used:
```cs
namespace GraphQL.Attachments;public class QueryExecutor
{
HttpClient client;
string uri;public QueryExecutor(HttpClient client, string uri = "graphql")
{
Guard.AgainstNullWhiteSpace(uri);this.client = client;
this.uri = uri;
}public Task ExecutePost(string query, Cancel cancel = default)
{
Guard.AgainstNullWhiteSpace(query);
return ExecutePost(new PostRequest(query), cancel);
}public async Task ExecutePost(PostRequest request, Cancel cancel = default)
{
using var content = new MultipartFormDataContent();
content.AddQueryAndVariables(request.Query, request.Variables, request.OperationName);if (request.Action != null)
{
var postContext = new PostContext(content);
request.Action?.Invoke(postContext);
postContext.HeadersAction?.Invoke(content.Headers);
}var response = await client.PostAsync(uri, content, cancel);
var result = await response.ProcessResponse(cancel);
return new(result.Stream, result.Attachments, response.Content.Headers, response.Headers, response.StatusCode);
}public Task ExecuteGet(string query, Cancel cancel = default)
{
Guard.AgainstNullWhiteSpace(query);
return ExecuteGet(new GetRequest(query), cancel);
}public async Task ExecuteGet(GetRequest request, Cancel cancel = default)
{
var compressed = Compress.Query(request.Query);
var variablesString = RequestAppender.ToJson(request.Variables);
var getUri = UriBuilder.GetUri(uri, variablesString, compressed, request.OperationName);using var getRequest = new HttpRequestMessage(HttpMethod.Get, getUri);
request.HeadersAction?.Invoke(getRequest.Headers);
var response = await client.SendAsync(getRequest, cancel);
return await response.ProcessResponse(cancel);
}
}
```
snippet source | anchorThis can be useful when performing [Integration testing in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/testing/integration-testing).
## Icon
memory designed by H Alberto Gongora from [The Noun Project](https://thenounproject.com)