From da2c4c69dd12b93e7ec85eb17d8c093b57c951ad Mon Sep 17 00:00:00 2001 From: Shane32 Date: Tue, 23 Jan 2024 17:22:23 -0500 Subject: [PATCH 1/9] Add attribute for media type --- Directory.Build.props | 2 +- samples/Samples.Upload/Mutation.cs | 3 +- .../AuthorizationVisitorBase.Validation.cs | 8 +- .../MediaTypeAttribute.cs | 136 ++++++++++++++++++ .../Transports.AspNetCore.csproj | 2 +- tests/Samples.Upload.Tests/EndToEndTests.cs | 33 +++++ 6 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 src/Transports.AspNetCore/MediaTypeAttribute.cs diff --git a/Directory.Build.props b/Directory.Build.props index d9bfc186..5987f5fe 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -32,7 +32,7 @@ GraphQL.Server.$(MSBuildProjectName) GraphQL.Server.$(MSBuildProjectName) - 7.6.0 + 8.0.0-preview-975 true <_FriendAssembliesPublicKey>PublicKey=0024000004800000940000000602000000240000525341310004000001000100352162dbf27be78fc45136884b8f324aa9f1dfc928c96c24704bf1df1a8779b2f26c760ed8321eca5b95ea6bd9bb60cd025b300f73bd1f4ae1ee6e281f85c527fa013ab5cb2c3fc7a1cbef7f9bf0c9014152e6a21f6e0ac6a371f8b45c6d7139c9119df9eeecf1cf59063545bb7c07437b1bc12be2c57d108d72d6c27176fbb8 diff --git a/samples/Samples.Upload/Mutation.cs b/samples/Samples.Upload/Mutation.cs index 42cb2263..76190fcb 100644 --- a/samples/Samples.Upload/Mutation.cs +++ b/samples/Samples.Upload/Mutation.cs @@ -1,4 +1,5 @@ using GraphQL; +using GraphQL.Server.Transports.AspNetCore; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; @@ -6,7 +7,7 @@ namespace Samples.Upload; public class Mutation { - public static async Task Rotate(IFormFile file, CancellationToken cancellationToken) + public static async Task Rotate([MediaType("image/*")] IFormFile file, CancellationToken cancellationToken) { if (file == null || file.Length == 0) { diff --git a/src/Transports.AspNetCore/AuthorizationVisitorBase.Validation.cs b/src/Transports.AspNetCore/AuthorizationVisitorBase.Validation.cs index cef523c5..43a834a2 100644 --- a/src/Transports.AspNetCore/AuthorizationVisitorBase.Validation.cs +++ b/src/Transports.AspNetCore/AuthorizationVisitorBase.Validation.cs @@ -12,7 +12,7 @@ public virtual ValueTask ValidateSchemaAsync(ValidationContext context) /// /// Validate a node that is current within the context. /// - private ValueTask ValidateAsync(IProvideMetadata obj, ASTNode? node, ValidationContext context) + private ValueTask ValidateAsync(IMetadataReader obj, ASTNode? node, ValidationContext context) => ValidateAsync(BuildValidationInfo(node, obj, context)); /// @@ -21,7 +21,7 @@ private ValueTask ValidateAsync(IProvideMetadata obj, ASTNode? node, Valid /// The specified . /// The , or which has been matched to the node specified in . /// The validation context. - private static ValidationInfo BuildValidationInfo(ASTNode? node, IProvideMetadata obj, ValidationContext context) + private static ValidationInfo BuildValidationInfo(ASTNode? node, IMetadataReader obj, ValidationContext context) { IFieldType? parentFieldType = null; IGraphType? parentGraphType = null; @@ -52,7 +52,7 @@ private static ValidationInfo BuildValidationInfo(ASTNode? node, IProvideMetadat /// For graph types other than operations, the field where this type was referenced; for query arguments, the field to which this argument belongs. /// For graph types, the graph type for the field where this type was referenced; for field types, the graph type to which this field belongs; for query arguments, the graph type for the field to which this argument belongs. public readonly record struct ValidationInfo( - IProvideMetadata Obj, + IMetadataReader Obj, ASTNode? Node, IFieldType? ParentFieldType, IGraphType? ParentGraphType, @@ -65,7 +65,7 @@ public readonly record struct ValidationInfo( /// /// Validates authorization rules for the specified schema, graph, field or query argument. - /// Does not consider + /// Does not consider /// as this is handled elsewhere. /// Returns a value indicating if validation was successful for this node. /// diff --git a/src/Transports.AspNetCore/MediaTypeAttribute.cs b/src/Transports.AspNetCore/MediaTypeAttribute.cs new file mode 100644 index 00000000..29902d73 --- /dev/null +++ b/src/Transports.AspNetCore/MediaTypeAttribute.cs @@ -0,0 +1,136 @@ +using System.Collections; +using MediaTypeHeaderValue = Microsoft.Net.Http.Headers.MediaTypeHeaderValue; + +namespace GraphQL.Server.Transports.AspNetCore; + +/// +/// Ensures that the marked field argument or input object field is a valid media type +/// via the method +/// and supports wildcards such as "text/*". +/// +/// +/// Only checks values of type , or lists of . +/// Any other types of values will throw a run-time exception. +/// +public class MediaTypeAttribute : GraphQLAttribute +{ + private readonly MediaTypeHeaderValue[] _mimeTypes; + + /// + public MediaTypeAttribute(params string[] mimeTypes) + { + var types = MediaTypeHeaderValue.ParseList(mimeTypes); + _mimeTypes = types as MediaTypeHeaderValue[] ?? types.ToArray(); + } + + /// + public override void Modify(FieldType fieldType, bool isInputType) + { + var lists = fieldType.Type != null + ? CountNestedLists(fieldType.Type) + : CountNestedLists(fieldType.ResolvedType + ?? throw new InvalidOperationException($"No graph type set on field '{fieldType.Name}'.")); + fieldType.Validator += new Validator(lists, _mimeTypes).Validate; + } + + /// + public override void Modify(QueryArgument queryArgument) + { + var lists = queryArgument.Type != null + ? CountNestedLists(queryArgument.Type) + : CountNestedLists(queryArgument.ResolvedType + ?? throw new InvalidOperationException($"No graph type set on field '{queryArgument.Name}'.")); + queryArgument.Validate(new Validator(lists, _mimeTypes).Validate); + } + + private class Validator + { + private readonly int _lists; + private readonly MediaTypeHeaderValue[] _mediaTypes; + + public Validator(int lists, MediaTypeHeaderValue[] mediaTypes) + { + _lists = lists; + _mediaTypes = mediaTypes; + } + + public void Validate(object? obj) + { + Validate(obj, _lists); + } + + public void Validate(object? obj, int lists) + { + if (obj == null) + return; + if (lists == 0) + { + if (obj is IFormFile file) + ValidateMediaType(file); + else + throw new InvalidOperationException("Expected an IFormFile object."); + } + else if (obj is IEnumerable enumerable) + { + foreach (var item in enumerable) + { + Validate(item, lists - 1); + } + } + else + { + throw new InvalidOperationException("Expected a list."); + } + } + + public void ValidateMediaType(IFormFile? file) + { + if (file == null) + return; + var contentType = file.ContentType; + if (contentType == null) + return; + var mediaType = MediaTypeHeaderValue.Parse(contentType); + foreach (var validMediaType in _mediaTypes) + { + if (mediaType.IsSubsetOf(validMediaType)) + return; + } + throw new InvalidOperationException($"Invalid media type '{mediaType}'."); + } + } + private static int CountNestedLists(Type type) + { + if (!type.IsGenericType) + return 0; + + var typeDef = type.GetGenericTypeDefinition(); + + if (typeDef == typeof(ListGraphType<>)) + { + return 1 + CountNestedLists(type.GetGenericArguments()[0]); + } + + if (typeDef == typeof(NonNullGraphType<>)) + { + return CountNestedLists(type.GetGenericArguments()[0]); + } + + return 0; + } + + private static int CountNestedLists(IGraphType type) + { + if (type is ListGraphType listGraphType) + { + return 1 + CountNestedLists(listGraphType.ResolvedType ?? throw new InvalidOperationException($"ResolvedType not set for {listGraphType}.")); + } + + if (type is NonNullGraphType nonNullGraphType) + { + return CountNestedLists(nonNullGraphType.ResolvedType ?? throw new InvalidOperationException($"ResolvedType not set for {nonNullGraphType}.")); + } + + return 0; + } +} diff --git a/src/Transports.AspNetCore/Transports.AspNetCore.csproj b/src/Transports.AspNetCore/Transports.AspNetCore.csproj index 42259b2d..31a2981f 100644 --- a/src/Transports.AspNetCore/Transports.AspNetCore.csproj +++ b/src/Transports.AspNetCore/Transports.AspNetCore.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/Samples.Upload.Tests/EndToEndTests.cs b/tests/Samples.Upload.Tests/EndToEndTests.cs index 9a867d5f..b37a41b6 100644 --- a/tests/Samples.Upload.Tests/EndToEndTests.cs +++ b/tests/Samples.Upload.Tests/EndToEndTests.cs @@ -38,4 +38,37 @@ public async Task RotateImage() var ret = await response.Content.ReadAsStringAsync(); ret.ShouldBe("{\"data\":{\"rotate\":\"/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIACAAIAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5\\u002BgEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4\\u002BTl5ufo6ery8/T19vf4\\u002Bfr/2gAMAwEAAhEDEQA/APUNa8c6boGvppeorLFG8Kyi5UblXlgQwHzdh0B69sZrorS8tr\\u002B2We0uY54X\\u002B7LGwZWwcHBHHWvGfi\\u002Bf\\u002BKwh5z/oSf8Aob1x\\u002Bla1qOiXP2jTbuS3kPXbyrcEDKng4ycZBxXnzxjp1ZRkro\\u002BsocNxxeCp16MuWbV3fVPf5r8UfUGSME8etDMBk\\u002BleX\\u002BH/AIt2twyQ65AbZ\\u002Bc3EILRnqeV\\u002B8Ow43ZPoK9HtLy2v7ZLi0njmhfO2SNtynBxwRx1rtp1YVFeLPncZgMTg5cteDXn0fo9v62PGPi//wAjhB/15J/6G9cLb2093OsFtBLNM2cRxoXZsDJwBz0r3DxL8P18UeJYr\\u002B7vGgtEt1i8uJcyMwLk8ngDlexzz0610mlaFpmgW7R6bZRwI33ivLNgkjcx5OMnGelcE8HKpVlJ6K59RhuIqODwFOjBc00vRLfd/wCX3nmGg/CS\\u002BugJdcn\\u002ByJ/zxhIeQ9Ry3QdjxuyPQ16fpXh/TNBhaLTLOO3VsZK8s2CSNzHk4ycZ6VsU0Z9K7aVCFL4UfO47NcVjn\\u002B\\u002Blp2Wi\\u002B7r87n//2Q==\"}}"); } + + [Fact] + public async Task RotateImage_WrongType() + { + using var webApp = new WebApplicationFactory(); + var server = webApp.Server; + + using var client = server.CreateClient(); + var form = new MultipartFormDataContent(); + var operations = new + { + query = "mutation ($img: FormFile!) { rotate(file: $img) }", + variables = new { img = (string?)null }, + }; + form.Add(JsonContent.Create(operations), "operations"); + var map = new + { + file0 = new string[] { "variables.img" }, + }; + form.Add(JsonContent.Create(map), "map"); + // base 64 of hello world + var base64hello = "aGVsbG8gd29ybGQ="; + var triangle = Convert.FromBase64String(base64hello); + var triangleContent = new ByteArrayContent(triangle); + triangleContent.Headers.ContentType = new("text/text"); + form.Add(triangleContent, "file0", "hello-world.txt"); + using var request = new HttpRequestMessage(HttpMethod.Post, "/graphql"); + request.Content = form; + using var response = await client.SendAsync(request); + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + var ret = await response.Content.ReadAsStringAsync(); + ret.ShouldBe("{\"errors\":[{\"message\":\"Invalid value for argument \\u0027file\\u0027 of field \\u0027rotate\\u0027. Invalid media type \\u0027text/text\\u0027.\",\"locations\":[{\"line\":1,\"column\":43}],\"extensions\":{\"code\":\"INVALID_VALUE\",\"codes\":[\"INVALID_VALUE\",\"INVALID_OPERATION\"],\"number\":\"5.6\"}}]}"); + } } From 89cc7b09173bd7e820ecbb5398508d2ba26f0a68 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 3 Aug 2024 11:51:11 -0400 Subject: [PATCH 2/9] Update src/Transports.AspNetCore/Transports.AspNetCore.csproj --- src/Transports.AspNetCore/Transports.AspNetCore.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transports.AspNetCore/Transports.AspNetCore.csproj b/src/Transports.AspNetCore/Transports.AspNetCore.csproj index e1b2b78c..106219b2 100644 --- a/src/Transports.AspNetCore/Transports.AspNetCore.csproj +++ b/src/Transports.AspNetCore/Transports.AspNetCore.csproj @@ -8,7 +8,7 @@ - + From 7feead779c2bebf3a84b945bb1c21aee601ae949 Mon Sep 17 00:00:00 2001 From: Shane32 Date: Sat, 3 Aug 2024 11:57:52 -0400 Subject: [PATCH 3/9] Update --- src/Transports.AspNetCore/MediaTypeAttribute.cs | 5 +++-- .../GraphQL.Server.Transports.AspNetCore.approved.txt | 6 ++++++ .../GraphQL.Server.Transports.AspNetCore.approved.txt | 6 ++++++ .../GraphQL.Server.Transports.AspNetCore.approved.txt | 6 ++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Transports.AspNetCore/MediaTypeAttribute.cs b/src/Transports.AspNetCore/MediaTypeAttribute.cs index 29902d73..ef13f4bf 100644 --- a/src/Transports.AspNetCore/MediaTypeAttribute.cs +++ b/src/Transports.AspNetCore/MediaTypeAttribute.cs @@ -59,7 +59,7 @@ public void Validate(object? obj) Validate(obj, _lists); } - public void Validate(object? obj, int lists) + private void Validate(object? obj, int lists) { if (obj == null) return; @@ -83,7 +83,7 @@ public void Validate(object? obj, int lists) } } - public void ValidateMediaType(IFormFile? file) + private void ValidateMediaType(IFormFile? file) { if (file == null) return; @@ -99,6 +99,7 @@ public void ValidateMediaType(IFormFile? file) throw new InvalidOperationException($"Invalid media type '{mediaType}'."); } } + private static int CountNestedLists(Type type) { if (!type.IsGenericType) diff --git a/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt index 693815b1..d4eda1ba 100644 --- a/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -169,6 +169,12 @@ namespace GraphQL.Server.Transports.AspNetCore } 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 { diff --git a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt index 9167836f..ffa1da76 100644 --- a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -187,6 +187,12 @@ namespace GraphQL.Server.Transports.AspNetCore } 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 { diff --git a/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt index 246ddb6c..70369b81 100644 --- a/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -169,6 +169,12 @@ namespace GraphQL.Server.Transports.AspNetCore } 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 { From 6beef0e05e4139f3d5d9c9ee4d271cbdc84def4d Mon Sep 17 00:00:00 2001 From: Shane32 Date: Sat, 3 Aug 2024 12:01:36 -0400 Subject: [PATCH 4/9] Update --- src/Transports.AspNetCore/MediaTypeAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transports.AspNetCore/MediaTypeAttribute.cs b/src/Transports.AspNetCore/MediaTypeAttribute.cs index ef13f4bf..9bb91b69 100644 --- a/src/Transports.AspNetCore/MediaTypeAttribute.cs +++ b/src/Transports.AspNetCore/MediaTypeAttribute.cs @@ -40,7 +40,7 @@ public override void Modify(QueryArgument queryArgument) ? CountNestedLists(queryArgument.Type) : CountNestedLists(queryArgument.ResolvedType ?? throw new InvalidOperationException($"No graph type set on field '{queryArgument.Name}'.")); - queryArgument.Validate(new Validator(lists, _mimeTypes).Validate); + queryArgument.Validator += new Validator(lists, _mimeTypes).Validate; } private class Validator From 0b94927ac684bfa8721940a852350a2fa7746395 Mon Sep 17 00:00:00 2001 From: Shane32 Date: Sat, 3 Aug 2024 12:04:38 -0400 Subject: [PATCH 5/9] Update notes --- docs/migration/migration8.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/migration/migration8.md b/docs/migration/migration8.md index 7bb64bff..11c6bbe8 100644 --- a/docs/migration/migration8.md +++ b/docs/migration/migration8.md @@ -1,8 +1,9 @@ # Migrating from v7 to v8 -## Major changes and new features +## New features -None +- When using `FormFileGraphType` with type-first schemas, you may specify the allowed media + types for the file by using the new `[MediaType]` attribute on the argument or input object field. ## Breaking changes From 34a31e33650edf7666b96a65ea90d6f75313ac49 Mon Sep 17 00:00:00 2001 From: Shane32 Date: Sat, 3 Aug 2024 12:09:27 -0400 Subject: [PATCH 6/9] Update readme --- README.md | 11 ++++++----- docs/migration/migration8.md | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 98e76aab..603b4685 100644 --- a/README.md +++ b/README.md @@ -1001,11 +1001,12 @@ services.AddGraphQL(b => b .AddSystemTextJson()); ``` -Please see the 'Upload' sample for a demonstration of this technique. Note that -using the `FormFileGraphType` scalar requires that the uploaded files be sent only -via the `multipart/form-data` content type as attached files. If you wish to also -allow clients to send files as base-64 encoded strings, you can write a custom scalar -better suited to your needs. +Please see the 'Upload' sample for a demonstration of this technique, which also +demonstrates the use of the `MediaTypeAttribute` to restrict the allowable media +types that will be accepted. Note that using the `FormFileGraphType` scalar requires +that the uploaded files be sent only via the `multipart/form-data` content type as +attached files. If you wish to also allow clients to send files as base-64 encoded +strings, you can write a custom scalar better suited to your needs. ### Native AOT support diff --git a/docs/migration/migration8.md b/docs/migration/migration8.md index 11c6bbe8..4f2d4f5e 100644 --- a/docs/migration/migration8.md +++ b/docs/migration/migration8.md @@ -8,6 +8,6 @@ ## Breaking changes - The validation rules' signatures have changed slightly due to the underlying changes to the - GraphQL.NET library. Please see the GraphQL.NET v8 migration document for more information. -- The obsolete (v6 and prior) authorization validation rule has been removed. See the v7 migration + GraphQL.NET library. Please see the GraphQL.NET v8 migration document for more information. +- The obsolete (v6 and prior) authorization validation rule has been removed. See the v7 migration document for more information on how to migrate to the v7/v8 authorization validation rule. From 48d713f34108e36df2bfd9616b8771ed2a06a83c Mon Sep 17 00:00:00 2001 From: Shane32 Date: Sat, 3 Aug 2024 12:11:39 -0400 Subject: [PATCH 7/9] Update --- docs/migration/migration8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migration/migration8.md b/docs/migration/migration8.md index 6f877fe2..8e2b3667 100644 --- a/docs/migration/migration8.md +++ b/docs/migration/migration8.md @@ -14,4 +14,4 @@ ## Other changes -- GraphiQL has been bumped from 1.5.1 to 3.2.0 +- GraphiQL has been bumped from 1.5.1 to 3.2.0. From f293f2977d2e01dc982df1c8285a9cdd5ef37a1c Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 3 Aug 2024 12:59:50 -0400 Subject: [PATCH 8/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 603b4685..e1d2935e 100644 --- a/README.md +++ b/README.md @@ -1005,7 +1005,7 @@ Please see the 'Upload' sample for a demonstration of this technique, which also demonstrates the use of the `MediaTypeAttribute` to restrict the allowable media types that will be accepted. Note that using the `FormFileGraphType` scalar requires that the uploaded files be sent only via the `multipart/form-data` content type as -attached files. If you wish to also allow clients to send files as base-64 encoded +attached files. If you also wish to allow clients to send files as base-64 encoded strings, you can write a custom scalar better suited to your needs. ### Native AOT support From ac966347e0097635f9e6678a58b80a8a9f05ab95 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 3 Aug 2024 13:06:13 -0400 Subject: [PATCH 9/9] Update src/Transports.AspNetCore/MediaTypeAttribute.cs --- src/Transports.AspNetCore/MediaTypeAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transports.AspNetCore/MediaTypeAttribute.cs b/src/Transports.AspNetCore/MediaTypeAttribute.cs index 9bb91b69..a2291cd6 100644 --- a/src/Transports.AspNetCore/MediaTypeAttribute.cs +++ b/src/Transports.AspNetCore/MediaTypeAttribute.cs @@ -70,7 +70,7 @@ private void Validate(object? obj, int lists) else throw new InvalidOperationException("Expected an IFormFile object."); } - else if (obj is IEnumerable enumerable) + else if (obj is IEnumerable enumerable && obj is not string) { foreach (var item in enumerable) {