From 8242d09c1efcdf6aff9550c326df71989066547f Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Jan 2024 18:54:06 -0500 Subject: [PATCH 1/7] Refactor to inherit from GraphQLHttpMiddleware --- Directory.Build.props | 5 +- GraphQL.Upload.AspNetCore.sln | 11 +- README.md | 10 +- samples/FileUploadSample/Startup.cs | 6 +- .../ApplicationBuilderExtensions.cs | 89 ++--- .../GraphQL.Upload.AspNetCore.csproj | 11 +- .../GraphQLBuilderExtensions.cs | 14 + .../GraphQLUploadError.cs | 38 ++ .../GraphQLUploadFileMap.cs | 10 - .../GraphQLUploadMiddleware.cs | 333 +++++++----------- .../GraphQLUploadOptions.cs | 10 +- .../GraphQLUploadRequest.cs | 63 ---- ...aphQLUploadRequestDeserializationResult.cs | 10 - .../GraphQLUploadRequestDeserializer.cs | 75 ---- .../ServiceCollectionExtensions.cs | 15 - .../UploadGraphType.cs | 34 +- .../MiddlewareOptionsTests.cs | 14 +- .../TestBase.cs | 17 +- 18 files changed, 255 insertions(+), 510 deletions(-) create mode 100644 src/GraphQL.Upload.AspNetCore/GraphQLBuilderExtensions.cs create mode 100644 src/GraphQL.Upload.AspNetCore/GraphQLUploadError.cs delete mode 100644 src/GraphQL.Upload.AspNetCore/GraphQLUploadFileMap.cs delete mode 100644 src/GraphQL.Upload.AspNetCore/GraphQLUploadRequest.cs delete mode 100644 src/GraphQL.Upload.AspNetCore/GraphQLUploadRequestDeserializationResult.cs delete mode 100644 src/GraphQL.Upload.AspNetCore/GraphQLUploadRequestDeserializer.cs delete mode 100644 src/GraphQL.Upload.AspNetCore/ServiceCollectionExtensions.cs diff --git a/Directory.Build.props b/Directory.Build.props index 690eac1..8d70c6f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,11 +3,12 @@ latest enable + enable - 7.1.1 - 7.1.1 + 7.6.0 + 7.6.0 2.2.0 6.0.0 6.0.0 diff --git a/GraphQL.Upload.AspNetCore.sln b/GraphQL.Upload.AspNetCore.sln index c2b48bc..c86e1bb 100644 --- a/GraphQL.Upload.AspNetCore.sln +++ b/GraphQL.Upload.AspNetCore.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.329 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34309.116 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileUploadSample", "samples\FileUploadSample\FileUploadSample.csproj", "{58C0B73A-2436-4F56-89E0-BD5D22E00047}" EndProject @@ -13,6 +13,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{237BB3B8 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CC98F604-7F80-4718-A307-7AA63FF55361}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0D65FAD1-1899-4283-B1BB-6EAC4A097AA5}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/README.md b/README.md index b01049c..f4ffeb4 100644 --- a/README.md +++ b/README.md @@ -20,15 +20,15 @@ This middleware implementation **only** parses multipart requests. That's why we ```csharp public void ConfigureServices(IServiceCollection services) { - services.AddSingleton() - .AddGraphQLUpload() - .AddGraphQL(); + services.AddGraphQL(b => b + .AddSchema() + .AddSystemTextJson() + .AddGraphQLUpload()); } public void Configure(IApplicationBuilder app) { - app.UseGraphQLUpload() - .UseGraphQL(); + app.UseGraphQLUpload(); } ``` diff --git a/samples/FileUploadSample/Startup.cs b/samples/FileUploadSample/Startup.cs index 6b3b5ce..8a8caee 100644 --- a/samples/FileUploadSample/Startup.cs +++ b/samples/FileUploadSample/Startup.cs @@ -8,11 +8,11 @@ public class Startup public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); - services.AddSingleton(); - services.AddGraphQLUpload(); - services.AddGraphQL(builder => builder + services.AddGraphQL(builder => builder + .AddSchema() .AddGraphTypes() + .AddGraphQLUpload() .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = true) .AddSystemTextJson()); diff --git a/src/GraphQL.Upload.AspNetCore/ApplicationBuilderExtensions.cs b/src/GraphQL.Upload.AspNetCore/ApplicationBuilderExtensions.cs index 88d00a8..046c127 100644 --- a/src/GraphQL.Upload.AspNetCore/ApplicationBuilderExtensions.cs +++ b/src/GraphQL.Upload.AspNetCore/ApplicationBuilderExtensions.cs @@ -2,71 +2,40 @@ using GraphQL.Upload.AspNetCore; using Microsoft.AspNetCore.Http; -namespace Microsoft.AspNetCore.Builder +namespace Microsoft.AspNetCore.Builder; + +/// +/// Extension methods for adding to an application. +/// +public static class ApplicationBuilderExtensions { /// - /// Extension methods for adding to an application. + /// Adds the to handle file uploads in GraphQL requests. /// - public static class ApplicationBuilderExtensions + /// The implementation of to use + /// The application builder + /// The path to the GraphQL endpoint which defaults to '/graphql' + /// The received as parameter + public static IApplicationBuilder UseGraphQLUpload(this IApplicationBuilder builder, string path = "/graphql", Action? configureOptions = null) + where TSchema : ISchema { - /// - /// Adds the to handle file uploads in GraphQL requests. - /// - /// The implementation of to use - /// The application builder - /// The path to the GraphQL endpoint which defaults to '/graphql' - /// The received as parameter - public static IApplicationBuilder UseGraphQLUpload(this IApplicationBuilder builder, string path = "/graphql") - where TSchema : ISchema - { - return builder.UseGraphQLUpload(new PathString(path)); - } - - /// - /// Adds the to handle file uploads in GraphQL requests. - /// - /// The implementation of to use - /// The application builder - /// The path to the GraphQL endpoint - /// The received as parameter> - public static IApplicationBuilder UseGraphQLUpload(this IApplicationBuilder builder, PathString path) - where TSchema : ISchema - { - return builder.UseGraphQLUpload(path, new GraphQLUploadOptions()); - } - - /// - /// Adds the to handle file uploads in GraphQL requests. - /// - /// The implementation of to use - /// The application builder - /// The path to the GraphQL endpoint - /// A delegate that is used to configure the , which are passed to the - /// The received as parameter> - public static IApplicationBuilder UseGraphQLUpload(this IApplicationBuilder builder, PathString path, Action configureOptions) - where TSchema : ISchema - { - var options = new GraphQLUploadOptions(); - configureOptions(options); - - return builder.UseGraphQLUpload(path, options); - } + return builder.UseGraphQLUpload(new PathString(path), configureOptions); + } - /// - /// Adds the to handle file uploads in GraphQL requests. - /// - /// The implementation of to use - /// The application builder - /// The path to the GraphQL endpoint - /// The options used to configure the - /// The received as parameter> - public static IApplicationBuilder UseGraphQLUpload(this IApplicationBuilder builder, PathString path, GraphQLUploadOptions options) - where TSchema : ISchema - { - if (options is null) - throw new ArgumentNullException(nameof(options)); + /// + /// Adds the to handle file uploads in GraphQL requests. + /// + /// The implementation of to use + /// The application builder + /// The path to the GraphQL endpoint + /// A delegate that is used to configure the , which are passed to the + /// The received as parameter> + public static IApplicationBuilder UseGraphQLUpload(this IApplicationBuilder builder, PathString path, Action? configureOptions = null) + where TSchema : ISchema + { + var options = new GraphQLUploadOptions(); + configureOptions?.Invoke(options); - return builder.UseMiddleware>(options, path); - } + return builder.UseGraphQL>(path, options); } } \ No newline at end of file diff --git a/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj b/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj index 5cbf798..c04667e 100644 --- a/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj +++ b/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj @@ -1,8 +1,8 @@  - netstandard2.0;netstandard2.1;net6.0; - 3.0.3 + netstandard2.0;netcoreapp3.1 + 4.0.0 Jannik Lassahn https://github.com/JannikLassahn/graphql-dotnet-upload @@ -13,12 +13,7 @@ - - - - - - + diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLBuilderExtensions.cs b/src/GraphQL.Upload.AspNetCore/GraphQLBuilderExtensions.cs new file mode 100644 index 0000000..37a18c5 --- /dev/null +++ b/src/GraphQL.Upload.AspNetCore/GraphQLBuilderExtensions.cs @@ -0,0 +1,14 @@ +using GraphQL.DI; +using GraphQL.Upload.AspNetCore; + +namespace GraphQL; + +public static class GraphQLUploadExtensions +{ + public static IGraphQLBuilder AddGraphQLUpload(this IGraphQLBuilder builder) + { + builder.Services.Register(ServiceLifetime.Singleton); + + return builder; + } +} diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLUploadError.cs b/src/GraphQL.Upload.AspNetCore/GraphQLUploadError.cs new file mode 100644 index 0000000..a75a407 --- /dev/null +++ b/src/GraphQL.Upload.AspNetCore/GraphQLUploadError.cs @@ -0,0 +1,38 @@ +using System.Net; + +namespace GraphQL.Upload.AspNetCore; + +public class GraphQLUploadError : ExecutionError +{ + public HttpStatusCode HttpStatusCode { get; } + + public GraphQLUploadError(string message, HttpStatusCode statusCode = HttpStatusCode.BadRequest, Exception? innerException = null) + : base(message, innerException) + { + HttpStatusCode = statusCode; + } +} + +public class BadMapPathError : GraphQLUploadError +{ + public BadMapPathError(Exception? innerException = null) + : base("Invalid map path." + (innerException != null ? " " + innerException.Message : null), HttpStatusCode.BadRequest, innerException) + { + } +} + +public class TooManyFilesError : GraphQLUploadError +{ + public TooManyFilesError() + : base("File uploads exceeded.", HttpStatusCode.RequestEntityTooLarge) + { + } +} + +public class FileSizeExceededError : GraphQLUploadError +{ + public FileSizeExceededError() + : base("File size limit exceeded.", HttpStatusCode.RequestEntityTooLarge) + { + } +} diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLUploadFileMap.cs b/src/GraphQL.Upload.AspNetCore/GraphQLUploadFileMap.cs deleted file mode 100644 index b46aaa0..0000000 --- a/src/GraphQL.Upload.AspNetCore/GraphQLUploadFileMap.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace GraphQL.Upload.AspNetCore -{ - public class GraphQLUploadFileMap - { - public List Parts { get; set; } - public IFormFile File { get; set; } - } -} \ No newline at end of file diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLUploadMiddleware.cs b/src/GraphQL.Upload.AspNetCore/GraphQLUploadMiddleware.cs index cb617a2..e3fc4c2 100644 --- a/src/GraphQL.Upload.AspNetCore/GraphQLUploadMiddleware.cs +++ b/src/GraphQL.Upload.AspNetCore/GraphQLUploadMiddleware.cs @@ -1,268 +1,171 @@ -using GraphQL.Execution; +using GraphQL.Server.Transports.AspNetCore; +using GraphQL.Transport; using GraphQL.Types; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Hosting; +using System.Collections; +using System.Globalization; +using System.Text; -namespace GraphQL.Upload.AspNetCore +namespace GraphQL.Upload.AspNetCore; + +public class GraphQLUploadMiddleware : GraphQLHttpMiddleware + where TSchema : ISchema { - public class GraphQLUploadMiddleware - where TSchema : ISchema + private readonly GraphQLUploadOptions _options; + private readonly IGraphQLTextSerializer _serializer; + + public GraphQLUploadMiddleware( + RequestDelegate next, + IGraphQLTextSerializer serializer, + IDocumentExecuter documentExecuter, + IServiceScopeFactory serviceScopeFactory, + GraphQLUploadOptions options, + IHostApplicationLifetime hostApplicationLifetime) + : base(next, serializer, documentExecuter, serviceScopeFactory, options, hostApplicationLifetime) { - private const string DOCS_URL = "See: https://github.com/jaydenseric/graphql-multipart-request-spec."; - private readonly ILogger _logger; - private readonly RequestDelegate _next; - private readonly GraphQLUploadOptions _options; - private readonly GraphQLUploadRequestDeserializer _requestDeserializer; - private readonly string _graphQLPath; - public GraphQLUploadMiddleware(ILogger> logger, RequestDelegate next, - GraphQLUploadOptions options, PathString path, GraphQLUploadRequestDeserializer requestDeserializer) - { - _logger = logger; - _next = next ?? throw new ArgumentNullException(nameof(next)); - _options = options; - _requestDeserializer = requestDeserializer; - _graphQLPath = path; - } - - public async Task InvokeAsync(HttpContext context) - { - if (!context.Request.HasFormContentType - || !context.Request.Path.StartsWithSegments(_graphQLPath)) - { - // not graphql path, eg. Form Authentication, skip this middleware - await _next(context); - return; - } - - // Handle requests as per recommendation at http://graphql.org/learn/serving-over-http/ - // Inspiration: https://github.com/graphql/express-graphql/blob/master/src/index.js - var httpRequest = context.Request; - var httpResponse = context.Response; + _options = options; + _serializer = serializer; + } - var serializer = context.RequestServices.GetRequiredService(); - var cancellationToken = GetCancellationToken(context); + protected override async Task<(GraphQLRequest? SingleRequest, IList? BatchRequest)?> ReadPostContentAsync(HttpContext context, RequestDelegate next, string? mediaType, Encoding? sourceEncoding) + { + if (!context.Request.HasFormContentType || !_options.ReadFormOnPost) + return await base.ReadPostContentAsync(context, next, mediaType, sourceEncoding); - // GraphQL HTTP only supports GET and POST methods - bool isPost = HttpMethods.IsPost(httpRequest.Method); - if (!isPost) - { - httpResponse.Headers["Allow"] = "POST"; - await WriteErrorResponseAsync(httpResponse, serializer, - $"Invalid HTTP method. Only POST are supported. {DOCS_URL}", - httpStatusCode: 405 // Method Not Allowed - ); - return; - } + try + { + var formCollection = await context.Request.ReadFormAsync(context.RequestAborted); + var deserializationResult = _serializer.Deserialize>(formCollection["operations"]); + if (deserializationResult == null) + return (null, null); - var form = await context.Request.ReadFormAsync(); + var map = _serializer.Deserialize>(formCollection["map"]); + if (map != null) + ApplyMapToRequests(map, formCollection, deserializationResult); - int statusCode = 400; - string error; - GraphQLUploadRequestDeserializationResult uploadRequest; - IFormFileCollection files; - List requests; + if (deserializationResult is GraphQLRequest[] array && array.Length == 1) + return (deserializationResult[0], null); + else + return (null, deserializationResult); + } + catch (GraphQLUploadError ex) + { + await WriteErrorResponseAsync(context, ex.HttpStatusCode, ex); + return null; + } + catch (Exception ex) + { + if (!await HandleDeserializationErrorAsync(context, next, ex)) + throw; + return null; + } - try - { - uploadRequest = _requestDeserializer.DeserializeFromFormCollection(form); - } - catch (Exception exception) - { - await WriteErrorResponseAsync(httpResponse, serializer, $"{exception.Message} ${DOCS_URL}", statusCode); - return; - } + void ApplyMapToRequests(Dictionary map, IFormCollection form, IList requests) + { + if (_options.MaximumFileCount.HasValue && form.Files.Count > _options.MaximumFileCount.Value) + throw new TooManyFilesError(); - (files, error, statusCode) = GetFiles(form); - if (error != null) + foreach (var file in form.Files) { - await WriteErrorResponseAsync(httpResponse, serializer, error, statusCode); - return; + if (_options.MaximumFileSize.HasValue && _options.MaximumFileSize.Value < file.Length) + throw new FileSizeExceededError(); } - (requests, error) = ExtractGraphQLRequests(uploadRequest, form); - if (error != null) + try { - await WriteErrorResponseAsync(httpResponse, serializer, error, statusCode); - return; - } - - var executer = context.RequestServices.GetRequiredService(); - var schema = context.RequestServices.GetRequiredService(); - - var results = await Task.WhenAll( - requests.Select(request => executer.ExecuteAsync(options => + foreach (var entry in map) { - options.CancellationToken = context.RequestAborted; - options.Schema = schema; - options.Query = request.Query; - options.OperationName = request.OperationName; - options.Variables = request.GetVariables(); - options.User = context.User; - if (_options.UserContextFactory != null) - { - options.UserContext = _options.UserContextFactory.Invoke(context); - } - options.RequestServices = context.RequestServices; - foreach (var listener in context.RequestServices.GetRequiredService>()) + var file = form.Files[entry.Key]; + if (file == null) continue; + foreach (var target in entry.Value) { - options.Listeners.Add(listener); + if (target != null) + ApplyFileToRequests(file, target, requests); } - }))); - - await WriteResponseAsync(context, serializer, results); - } - - protected virtual CancellationToken GetCancellationToken(HttpContext context) => context.RequestAborted; - - private static GraphQLUploadRequest CreateGraphQLRequest(GraphQLUploadRequest operation, Dictionary> metaLookup, int index) - { - if (metaLookup.ContainsKey(index)) - { - operation.TokensToReplace = metaLookup[index]; - } - - return operation; - } - - private static (int requestIndex, List parts) GetParts(string path, bool isBatchedRequest) - { - var results = new List(); - var requestIndex = 0; - - foreach (var key in path.Split('.')) - { - if (int.TryParse(key, out int integer)) - { - results.Add(integer); - } - else - { - results.Add(key); } } - - // remove request index and 'variables' part, - // because the parts list only needs to hold the parts relevant for each request - // e.g: 0.variables.file.0 -> ["file", 0] - if (isBatchedRequest) + catch (ExecutionError) { - requestIndex = (int)results[0]; - results.RemoveRange(0, 2); + throw; } - else + catch (Exception ex) { - results.RemoveAt(0); + throw new BadMapPathError(ex); } - - return (requestIndex, results); } - private (IFormFileCollection, string, int) GetFiles(IFormCollection form) + void ApplyFileToRequests(IFormFile file, string target, IList requests) { - if (!form.Files.Any()) - { - return (null, $"No files attached. {DOCS_URL}", 400); - } - - // validate file count - if (_options.MaximumFileCount < form.Files.Count) + if (target.StartsWith("variables.", StringComparison.Ordinal)) { - return (null, $"{_options.MaximumFileCount} file uploads exceeded.", 413); - } - - // validate file size - foreach (var file in form.Files) - { - if (file.Length > _options.MaximumFileSize) - { - return (null, "File size limit exceeded.", 413); - } + if (requests.Count < 1) + throw new BadMapPathError(); + ApplyFileToRequest(file, target.Substring(10), requests[0]); + return; } - - return (form.Files, null, default); + var i = target.IndexOf('.'); + if (i == -1 || !string.Equals(target.Substring(i + 1, 10), "variables.", StringComparison.Ordinal)) + throw new BadMapPathError(); + if (!int.TryParse(target.Substring(0, i), NumberStyles.Integer, CultureInfo.InvariantCulture, out var index)) + throw new BadMapPathError(); + if (requests.Count < (index + 1)) + throw new BadMapPathError(); + ApplyFileToRequest(file, target.Substring(10 + i + 1), requests[index]); } - private Task WriteErrorResponseAsync(HttpResponse httpResponse, IGraphQLTextSerializer serializer, - string errorMessage, int httpStatusCode = 400 /* BadRequest */) + void ApplyFileToRequest(IFormFile file, string target, GraphQLRequest? request) { - var result = new ExecutionResult + var inputs = new Dictionary(request?.Variables ?? throw new BadMapPathError()); + object parent = inputs; + string? prop = null; + foreach (var location in target.Split('.')) { - Errors = new ExecutionErrors + if (prop == null) { - new ExecutionError(errorMessage) + prop = location; + continue; } - }; - - httpResponse.ContentType = "application/json"; - httpResponse.StatusCode = httpStatusCode; - - return serializer.WriteAsync(httpResponse.Body, result); - } - - private async Task WriteResponseAsync(HttpContext context, IGraphQLTextSerializer serializer, ExecutionResult[] results) - { - context.Response.ContentType = "application/json"; - context.Response.StatusCode = 200; - - foreach (var result in results) - { - if (result.Errors != null) + if (parent is IList list) { - _logger.LogError("GraphQL execution error(s): {Errors}", result.Errors); + parent = list[int.Parse(prop)] ?? throw new BadMapPathError(); } - } - - if (results.Length == 1) - { - await serializer.WriteAsync(context.Response.Body, results[0]); - return; - } - - await serializer.WriteAsync(context.Response.Body, results); - } - - private (List requests, string error) ExtractGraphQLRequests( - GraphQLUploadRequestDeserializationResult operations, IFormCollection forms) - { - List requests; - var metaLookup = new Dictionary>(); - - foreach (var entry in operations.Map) - { - var file = forms.Files.GetFile(entry.Key); - if (file is null) + else if (parent is IReadOnlyDictionary dic) { - return (null, "File is null"); + parent = dic[prop] ?? throw new BadMapPathError(); } - - foreach (var path in entry.Value) + else { - (var index, var parts) = GetParts(path, operations.Batch != default); - - if (!metaLookup.ContainsKey(index)) - { - metaLookup.Add(index, new List()); - } - - metaLookup[index].Add(new GraphQLUploadFileMap { File = file, Parts = parts }); + throw new BadMapPathError(); } + prop = location; } - if (operations.Batch != default) + // verify that the target is valid + if (prop == null || prop.Length == 0) + throw new BadMapPathError(); + + // set the target to the form file + if (parent is IList list2) + { + if (list2[int.Parse(prop)] != null) + throw new BadMapPathError(); + list2[int.Parse(prop)] = file; + } + else if (parent is IDictionary dic) { - int i = 0; - requests = operations.Batch - .Select(x => CreateGraphQLRequest(x, metaLookup, i++)) - .ToList(); + if (dic[prop] != null) + throw new BadMapPathError(); + dic[prop] = file; } else { - var request = CreateGraphQLRequest(operations.Single, metaLookup, 0); - requests = new List { request }; + throw new BadMapPathError(); } - return (requests, null); + // set inputs + request!.Variables = new Inputs(inputs); } } } diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLUploadOptions.cs b/src/GraphQL.Upload.AspNetCore/GraphQLUploadOptions.cs index 14b715b..44476e3 100644 --- a/src/GraphQL.Upload.AspNetCore/GraphQLUploadOptions.cs +++ b/src/GraphQL.Upload.AspNetCore/GraphQLUploadOptions.cs @@ -1,11 +1,12 @@ -using Microsoft.AspNetCore.Http; +using GraphQL.Server.Transports.AspNetCore; +using Microsoft.AspNetCore.Http; namespace GraphQL.Upload.AspNetCore { /// /// Options for . /// - public class GraphQLUploadOptions + public class GraphQLUploadOptions : GraphQLHttpMiddlewareOptions { /// /// The maximum allowed file size in bytes. Null indicates no limit at all. @@ -16,10 +17,5 @@ public class GraphQLUploadOptions /// The maximum allowed amount of files. Null indicates no limit at all. /// public long? MaximumFileCount { get; set; } - - /// - /// Gets or sets the user context factory. - /// - public Func> UserContextFactory { get; set; } } } diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLUploadRequest.cs b/src/GraphQL.Upload.AspNetCore/GraphQLUploadRequest.cs deleted file mode 100644 index 2d345a3..0000000 --- a/src/GraphQL.Upload.AspNetCore/GraphQLUploadRequest.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Text.Json.Serialization; - -namespace GraphQL.Upload.AspNetCore -{ - public class GraphQLUploadRequest - { - public const string OPERATION_NAME_KEY = "operationName"; - public const string QUERY_KEY = "query"; - public const string VARIABLES_KEY = "variables"; - - [JsonPropertyName(GraphQLUploadRequest.OPERATION_NAME_KEY)] - public string OperationName { get; set; } - - [JsonPropertyName(GraphQLUploadRequest.QUERY_KEY)] - public string Query { get; set; } - - [JsonPropertyName(GraphQLUploadRequest.VARIABLES_KEY)] - public Inputs Variables { get; set; } - - public List TokensToReplace { get; set; } - - public Inputs GetVariables() - { - var variables = new Dictionary(Variables); - - foreach (var info in TokensToReplace) - { - object variableSection = variables; - - for (var i = 0; i < info.Parts.Count; i++) - { - var part = info.Parts[i]; - var isLast = i == info.Parts.Count - 1; - - if (part is string key) - { - if (isLast) - { - ((Dictionary)variableSection)[key] = info.File; - } - else - { - variableSection = ((Dictionary)variableSection)[key]; - } - } - else if (part is int index) - { - if (isLast) - { - ((List)variableSection)[index] = info.File; - } - else - { - variableSection = ((List)variableSection)[index]; - } - } - } - } - - return variables.ToInputs(); - } - } -} diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLUploadRequestDeserializationResult.cs b/src/GraphQL.Upload.AspNetCore/GraphQLUploadRequestDeserializationResult.cs deleted file mode 100644 index 1e98b71..0000000 --- a/src/GraphQL.Upload.AspNetCore/GraphQLUploadRequestDeserializationResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GraphQL.Upload.AspNetCore -{ - public class GraphQLUploadRequestDeserializationResult - { - public bool IsSuccessful { get; set; } - public GraphQLUploadRequest Single { get; set; } - public GraphQLUploadRequest[] Batch { get; set; } - public Dictionary Map { get; set; } - } -} \ No newline at end of file diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLUploadRequestDeserializer.cs b/src/GraphQL.Upload.AspNetCore/GraphQLUploadRequestDeserializer.cs deleted file mode 100644 index 684e041..0000000 --- a/src/GraphQL.Upload.AspNetCore/GraphQLUploadRequestDeserializer.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Microsoft.AspNetCore.Http; -using System.Text.Json; - -namespace GraphQL.Upload.AspNetCore -{ - public class GraphQLUploadRequestDeserializer - { - private readonly IGraphQLTextSerializer _graphQLTextSerializer; - - public GraphQLUploadRequestDeserializer(IGraphQLTextSerializer graphQLTextSerializer) - { - _graphQLTextSerializer = graphQLTextSerializer; - } - - public GraphQLUploadRequestDeserializationResult DeserializeFromFormCollection(IFormCollection form) - { - var result = new GraphQLUploadRequestDeserializationResult { IsSuccessful = true }; - - SetOperations(result, form); - SetMap(result, form); - - return result; - } - - private void SetOperations(GraphQLUploadRequestDeserializationResult result, IFormCollection form) - { - if (!form.TryGetValue("operations", out var operations)) - { - throw new Exception("Missing field 'operations'."); - } - - var firstChar = operations[0][0]; - var isBatched = false; - - if (firstChar == '[') - { - isBatched = true; - } - - try - { - if (isBatched) - { - result.Batch = _graphQLTextSerializer.Deserialize(operations) - .ToArray(); - } - else - { - result.Single = _graphQLTextSerializer.Deserialize(operations); - } - } - catch - { - throw new Exception("Invalid JSON in the 'operations' Upload field."); - } - } - - private void SetMap(GraphQLUploadRequestDeserializationResult result, IFormCollection form) - { - if (!form.TryGetValue("map", out var map)) - { - throw new Exception("Missing field 'map'"); - } - - try - { - result.Map = JsonSerializer.Deserialize>(map); - } - catch (JsonException) - { - throw new Exception("Invalid JSON in the 'map' Upload field."); - } - } - } -} diff --git a/src/GraphQL.Upload.AspNetCore/ServiceCollectionExtensions.cs b/src/GraphQL.Upload.AspNetCore/ServiceCollectionExtensions.cs deleted file mode 100644 index edab8fe..0000000 --- a/src/GraphQL.Upload.AspNetCore/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using GraphQL.Upload.AspNetCore; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class ServiceCollectionExtensions - { - public static IServiceCollection AddGraphQLUpload(this IServiceCollection services) - { - services.AddSingleton(); - services.AddSingleton(); - - return services; - } - } -} diff --git a/src/GraphQL.Upload.AspNetCore/UploadGraphType.cs b/src/GraphQL.Upload.AspNetCore/UploadGraphType.cs index b505b16..ce06074 100644 --- a/src/GraphQL.Upload.AspNetCore/UploadGraphType.cs +++ b/src/GraphQL.Upload.AspNetCore/UploadGraphType.cs @@ -1,21 +1,27 @@ using GraphQL.Types; +using GraphQLParser.AST; using Microsoft.AspNetCore.Http; -namespace GraphQL.Upload.AspNetCore +namespace GraphQL.Upload.AspNetCore; + +public class UploadGraphType : ScalarGraphType { - public class UploadGraphType : ScalarGraphType + public UploadGraphType() { - public UploadGraphType() - { - Name = "Upload"; - Description = "A meta type that represents a file upload."; - } - - public override object ParseValue(object value) => value switch - { - IFormFile _ => value, - null => null, - _ => ThrowValueConversionError(value) - }; + Name = "Upload"; + Description = "A meta type that represents a file upload."; } + + public override bool CanParseLiteral(GraphQLValue value) => false; + + public override object? ParseLiteral(GraphQLValue value) => ThrowLiteralConversionError(value, "Upload files must be passed through variables."); + + public override bool CanParseValue(object? value) => value is IFormFile || value == null; + + public override object? ParseValue(object? value) => value switch + { + IFormFile _ => value, + null => null, + _ => ThrowValueConversionError(value) + }; } diff --git a/tests/GraphQL.Upload.AspNetCore.Tests/MiddlewareOptionsTests.cs b/tests/GraphQL.Upload.AspNetCore.Tests/MiddlewareOptionsTests.cs index 721391a..724d11b 100644 --- a/tests/GraphQL.Upload.AspNetCore.Tests/MiddlewareOptionsTests.cs +++ b/tests/GraphQL.Upload.AspNetCore.Tests/MiddlewareOptionsTests.cs @@ -20,12 +20,7 @@ public async Task TooManyFiles() { fileA, "0", "a.txt" } }; - var options = new GraphQLUploadOptions - { - MaximumFileCount = 0 - }; - - using (var server = CreateServer(options)) + using (var server = CreateServer(options => options.MaximumFileCount = 0)) { // Act var client = server.CreateClient(); @@ -54,12 +49,7 @@ public async Task TooLargeFile() { fileB, "1", "b.txt" } }; - var options = new GraphQLUploadOptions - { - MaximumFileSize = 2 - }; - - using (var server = CreateServer(options)) + using (var server = CreateServer(options => options.MaximumFileSize = 2)) { // Act var client = server.CreateClient(); diff --git a/tests/GraphQL.Upload.AspNetCore.Tests/TestBase.cs b/tests/GraphQL.Upload.AspNetCore.Tests/TestBase.cs index e6fe9df..f524bbc 100644 --- a/tests/GraphQL.Upload.AspNetCore.Tests/TestBase.cs +++ b/tests/GraphQL.Upload.AspNetCore.Tests/TestBase.cs @@ -12,21 +12,20 @@ namespace GraphQL.Upload.AspNetCore.Tests { public abstract class TestBase { - public TestServer CreateServer(GraphQLUploadOptions options = null) + public TestServer CreateServer(Action? options = null) { - var path = Assembly.GetAssembly(typeof(TestBase)).Location; + var path = Assembly.GetCallingAssembly().Location; var hostBuilder = new WebHostBuilder() - .UseContentRoot(Path.GetDirectoryName(path)) + .UseContentRoot(Path.GetDirectoryName(path)!) .ConfigureServices(services => - services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddGraphQLUpload() + services.AddGraphQL(b => b + .AddSchema() + .AddSystemTextJson() + .AddGraphQLUpload()) ) .Configure(app => - app.UseGraphQLUpload("/graphql", options ?? new GraphQLUploadOptions()) + app.UseGraphQLUpload("/graphql", options) ); return new TestServer(hostBuilder); From 5358e89df4221049eea47550af42cb6eeab1e1b4 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Jan 2024 18:58:48 -0500 Subject: [PATCH 2/7] Update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f4ffeb4..b723b3e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Preview versions from the develop branch are available via [GitHub Packages](htt Register the middleware in your Startup.cs. -This middleware implementation **only** parses multipart requests. That's why we're using additional middleware ([graphql-dotnet/server](https://github.com/graphql-dotnet/server)) to handle other request types. +This middleware inherits from `GraphQLHttpMiddleware` and as such supports all of the base functionality provied by `GraphQL.Server.Transports.AspNetCore`. + ```csharp public void ConfigureServices(IServiceCollection services) { From 56dbe478f9b65dea6f21c1da7777d499a7d65a6b Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Jan 2024 21:26:38 -0500 Subject: [PATCH 3/7] Update --- .github/workflows/ci.yml | 3 ++- samples/FileUploadSample/FileUploadSample.csproj | 1 + .../GraphQL.Upload.AspNetCore.csproj | 5 +++++ .../GraphQL.Upload.AspNetCore.Tests.csproj | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99be28f..564b5de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,12 +14,13 @@ jobs: - uses: actions/checkout@v3 - name: Setup .NET Core SDKs - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 3.1.x 5.0.x 6.0.x + 8.0.x - name: Restore run: | diff --git a/samples/FileUploadSample/FileUploadSample.csproj b/samples/FileUploadSample/FileUploadSample.csproj index 758daee..b0177f0 100644 --- a/samples/FileUploadSample/FileUploadSample.csproj +++ b/samples/FileUploadSample/FileUploadSample.csproj @@ -2,6 +2,7 @@ net6.0 + false diff --git a/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj b/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj index c04667e..3f66627 100644 --- a/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj +++ b/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj @@ -7,13 +7,18 @@ https://github.com/JannikLassahn/graphql-dotnet-upload MIT + README.md https://github.com/JannikLassahn/graphql-dotnet-upload Middleware and an Upload scalar to add support for GraphQL multipart requests for ASP.NET Core ASP.NET Core, GraphQL, File Upload + embedded + + + diff --git a/tests/GraphQL.Upload.AspNetCore.Tests/GraphQL.Upload.AspNetCore.Tests.csproj b/tests/GraphQL.Upload.AspNetCore.Tests/GraphQL.Upload.AspNetCore.Tests.csproj index 1b448f2..492d743 100644 --- a/tests/GraphQL.Upload.AspNetCore.Tests/GraphQL.Upload.AspNetCore.Tests.csproj +++ b/tests/GraphQL.Upload.AspNetCore.Tests/GraphQL.Upload.AspNetCore.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;net5.0;net6.0 + netcoreapp3.1;net5.0;net6.0;net8.0 false From 2f6d921a2a2e780be92fe69f28e25d3b326e1557 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Jan 2024 21:36:54 -0500 Subject: [PATCH 4/7] Update --- .../FileUploadSample/FileUploadSample.csproj | 2 +- samples/FileUploadSample/Program.cs | 48 +++++++++++++------ samples/FileUploadSample/Startup.cs | 43 ----------------- .../ApplicationBuilderExtensions.cs | 8 ++++ 4 files changed, 43 insertions(+), 58 deletions(-) delete mode 100644 samples/FileUploadSample/Startup.cs diff --git a/samples/FileUploadSample/FileUploadSample.csproj b/samples/FileUploadSample/FileUploadSample.csproj index b0177f0..ba09785 100644 --- a/samples/FileUploadSample/FileUploadSample.csproj +++ b/samples/FileUploadSample/FileUploadSample.csproj @@ -2,7 +2,7 @@ net6.0 - false + false diff --git a/samples/FileUploadSample/Program.cs b/samples/FileUploadSample/Program.cs index ed170b2..ca44c7e 100644 --- a/samples/FileUploadSample/Program.cs +++ b/samples/FileUploadSample/Program.cs @@ -1,17 +1,37 @@ -using Microsoft.AspNetCore; +using FileUploadSample; +using GraphQL; +using GraphQL.Types; -namespace FileUploadSample +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSingleton(); + +builder.Services.AddGraphQL(builder => builder + .AddSchema() + .AddGraphTypes() + .AddGraphQLUpload() + .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = true) + .AddSystemTextJson()); + +builder.Services.AddCors(); + + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) { - public class Program - { - public static void Main(string[] args) - { - CreateWebHostBuilder(args).Build().Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseKestrel() - .UseStartup(); - } + app.UseDeveloperExceptionPage(); } + +app.UseStaticFiles(); + +app.UseCors(b => b + .AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + +// register the middleware +app.UseGraphQLUpload(); +app.UseGraphQLPlayground("/"); + +await app.RunAsync(); diff --git a/samples/FileUploadSample/Startup.cs b/samples/FileUploadSample/Startup.cs deleted file mode 100644 index 8a8caee..0000000 --- a/samples/FileUploadSample/Startup.cs +++ /dev/null @@ -1,43 +0,0 @@ -using GraphQL; -using GraphQL.Types; - -namespace FileUploadSample -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - - services.AddGraphQL(builder => builder - .AddSchema() - .AddGraphTypes() - .AddGraphQLUpload() - .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = true) - .AddSystemTextJson()); - - services.AddCors(); - } - - public void Configure(IApplicationBuilder app, IHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseStaticFiles(); - - app.UseCors(b => b - .AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader()); - - // register the middleware that can handle multipart requests first - app.UseGraphQLUpload(); - - app.UseGraphQL(); - app.UseGraphQLPlayground("/"); - } - } -} diff --git a/src/GraphQL.Upload.AspNetCore/ApplicationBuilderExtensions.cs b/src/GraphQL.Upload.AspNetCore/ApplicationBuilderExtensions.cs index 046c127..0860b2c 100644 --- a/src/GraphQL.Upload.AspNetCore/ApplicationBuilderExtensions.cs +++ b/src/GraphQL.Upload.AspNetCore/ApplicationBuilderExtensions.cs @@ -38,4 +38,12 @@ public static IApplicationBuilder UseGraphQLUpload(this IApplicationBui return builder.UseGraphQL>(path, options); } + + /// + public static IApplicationBuilder UseGraphQLUpload(this IApplicationBuilder builder, string path = "/graphql", Action? configureOptions = null) + => UseGraphQLUpload(builder, path, configureOptions); + + /// + public static IApplicationBuilder UseGraphQLUpload(this IApplicationBuilder builder, PathString path, Action? configureOptions = null) + => UseGraphQLUpload(builder, path, configureOptions); } \ No newline at end of file From d651e59f8e0dd6ab57b4baeae4eb10749fe6916c Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Jan 2024 22:00:52 -0500 Subject: [PATCH 5/7] Update --- .../BadMapPathError.cs | 18 ++++++++++ .../FileSizeExceededError.cs | 17 +++++++++ .../GraphQL.Upload.AspNetCore.csproj | 6 ++-- .../GraphQLBuilderExtensions.cs | 9 ++++- .../GraphQLUploadError.cs | 36 +++++++------------ .../TooManyFilesError.cs | 17 +++++++++ 6 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 src/GraphQL.Upload.AspNetCore/BadMapPathError.cs create mode 100644 src/GraphQL.Upload.AspNetCore/FileSizeExceededError.cs create mode 100644 src/GraphQL.Upload.AspNetCore/TooManyFilesError.cs diff --git a/src/GraphQL.Upload.AspNetCore/BadMapPathError.cs b/src/GraphQL.Upload.AspNetCore/BadMapPathError.cs new file mode 100644 index 0000000..be23632 --- /dev/null +++ b/src/GraphQL.Upload.AspNetCore/BadMapPathError.cs @@ -0,0 +1,18 @@ +using System.Net; + +namespace GraphQL.Upload.AspNetCore; + +/// +/// Represents an error when an invalid map path is provided in a GraphQL file upload request. +/// +public class BadMapPathError : GraphQLUploadError +{ + /// + /// Initializes a new instance of the class. + /// + /// The inner exception, if any, that caused the bad map path error. + public BadMapPathError(Exception? innerException = null) + : base("Invalid map path." + (innerException != null ? " " + innerException.Message : null), HttpStatusCode.BadRequest, innerException) + { + } +} diff --git a/src/GraphQL.Upload.AspNetCore/FileSizeExceededError.cs b/src/GraphQL.Upload.AspNetCore/FileSizeExceededError.cs new file mode 100644 index 0000000..b3c951a --- /dev/null +++ b/src/GraphQL.Upload.AspNetCore/FileSizeExceededError.cs @@ -0,0 +1,17 @@ +using System.Net; + +namespace GraphQL.Upload.AspNetCore; + +/// +/// Represents an error when a file exceeds the allowed size limit in a GraphQL upload. +/// +public class FileSizeExceededError : GraphQLUploadError +{ + /// + /// Initializes a new instance of the class. + /// + public FileSizeExceededError() + : base("File size limit exceeded.", HttpStatusCode.RequestEntityTooLarge) + { + } +} diff --git a/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj b/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj index 3f66627..a594ffa 100644 --- a/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj +++ b/src/GraphQL.Upload.AspNetCore/GraphQL.Upload.AspNetCore.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLBuilderExtensions.cs b/src/GraphQL.Upload.AspNetCore/GraphQLBuilderExtensions.cs index 37a18c5..82e58b3 100644 --- a/src/GraphQL.Upload.AspNetCore/GraphQLBuilderExtensions.cs +++ b/src/GraphQL.Upload.AspNetCore/GraphQLBuilderExtensions.cs @@ -3,12 +3,19 @@ namespace GraphQL; +/// +/// Provides extension methods for setting up GraphQL file upload support in an application. +/// public static class GraphQLUploadExtensions { + /// + /// Registers within the dependency injection framework + /// as a singleton for use within a GraphQL schema. + /// public static IGraphQLBuilder AddGraphQLUpload(this IGraphQLBuilder builder) { builder.Services.Register(ServiceLifetime.Singleton); - + return builder; } } diff --git a/src/GraphQL.Upload.AspNetCore/GraphQLUploadError.cs b/src/GraphQL.Upload.AspNetCore/GraphQLUploadError.cs index a75a407..4800160 100644 --- a/src/GraphQL.Upload.AspNetCore/GraphQLUploadError.cs +++ b/src/GraphQL.Upload.AspNetCore/GraphQLUploadError.cs @@ -2,37 +2,25 @@ namespace GraphQL.Upload.AspNetCore; +/// +/// Represents errors related to GraphQL file uploads. +/// public class GraphQLUploadError : ExecutionError { + /// + /// Gets the HTTP status code associated with the error. + /// public HttpStatusCode HttpStatusCode { get; } + /// + /// Initializes a new instance of the class. + /// + /// The error message. + /// The HTTP status code (defaults to BadRequest). + /// The inner exception, if any. public GraphQLUploadError(string message, HttpStatusCode statusCode = HttpStatusCode.BadRequest, Exception? innerException = null) : base(message, innerException) { HttpStatusCode = statusCode; } } - -public class BadMapPathError : GraphQLUploadError -{ - public BadMapPathError(Exception? innerException = null) - : base("Invalid map path." + (innerException != null ? " " + innerException.Message : null), HttpStatusCode.BadRequest, innerException) - { - } -} - -public class TooManyFilesError : GraphQLUploadError -{ - public TooManyFilesError() - : base("File uploads exceeded.", HttpStatusCode.RequestEntityTooLarge) - { - } -} - -public class FileSizeExceededError : GraphQLUploadError -{ - public FileSizeExceededError() - : base("File size limit exceeded.", HttpStatusCode.RequestEntityTooLarge) - { - } -} diff --git a/src/GraphQL.Upload.AspNetCore/TooManyFilesError.cs b/src/GraphQL.Upload.AspNetCore/TooManyFilesError.cs new file mode 100644 index 0000000..8a1b2aa --- /dev/null +++ b/src/GraphQL.Upload.AspNetCore/TooManyFilesError.cs @@ -0,0 +1,17 @@ +using System.Net; + +namespace GraphQL.Upload.AspNetCore; + +/// +/// Represents an error when too many files are uploaded in a GraphQL request. +/// +public class TooManyFilesError : GraphQLUploadError +{ + /// + /// Initializes a new instance of the class. + /// + public TooManyFilesError() + : base("File uploads exceeded.", HttpStatusCode.RequestEntityTooLarge) + { + } +} From 0f77cc17609645b69e645f08495fed3d60356d03 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Jan 2024 22:03:57 -0500 Subject: [PATCH 6/7] Update --- .../UploadGraphType.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/GraphQL.Upload.AspNetCore/UploadGraphType.cs b/src/GraphQL.Upload.AspNetCore/UploadGraphType.cs index ce06074..f758f63 100644 --- a/src/GraphQL.Upload.AspNetCore/UploadGraphType.cs +++ b/src/GraphQL.Upload.AspNetCore/UploadGraphType.cs @@ -4,20 +4,35 @@ namespace GraphQL.Upload.AspNetCore; +/// +/// Represents a GraphQL scalar type named 'Upload' for handling file uploads. +/// +/// +/// This scalar type is used to represent file uploads in a GraphQL schema. +/// It is designed to work with multipart form data in GraphQL requests. +/// public class UploadGraphType : ScalarGraphType { + /// + /// Initializes a new instance of the class. + /// public UploadGraphType() { Name = "Upload"; Description = "A meta type that represents a file upload."; } + /// public override bool CanParseLiteral(GraphQLValue value) => false; - public override object? ParseLiteral(GraphQLValue value) => ThrowLiteralConversionError(value, "Upload files must be passed through variables."); + /// + public override object? ParseLiteral(GraphQLValue value) + => ThrowLiteralConversionError(value, "Upload files must be passed through variables."); + /// public override bool CanParseValue(object? value) => value is IFormFile || value == null; + /// public override object? ParseValue(object? value) => value switch { IFormFile _ => value, From 81d3f810a27ca48e00da3a715711ad462346f7c3 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Jan 2024 22:16:51 -0500 Subject: [PATCH 7/7] Update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b723b3e..a2b8c6e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ public void ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app) { - app.UseGraphQLUpload(); + app.UseGraphQLUpload(); } ```