1

I have read the topic: ASP.NET Core : Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead but for my case I can't understand, how to fix it:

I have controller method:

    public async Task<IActionResult> Upload()
    {
        var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
        var reader = new MultipartReader(boundary.ToString(), HttpContext.Request.Body);

        var section = await reader.ReadNextSectionAsync();
        List<FileApi> uploadedFiles = new List<FileApi>();

        while (section != null)
        {
            ContentDispositionHeaderValue contentDisposition;
            var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);

            if (hasContentDispositionHeader)
            {
                if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                {
                    var fileDto = await _zendeskManageFileService.UploadFileAsync(contentDisposition.FileName.ToString(), section.Body);
                    uploadedFiles.Add(_mapper.Map<FileApi>(fileDto));
                }
                else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
                {
                    var errorNoFile = new ErrorResponse
                    {
                        Error = new Error
                        {
                            Code = "NoFile",
                            Message = $"Expected a file, no form data content",
                        }
                    };

                    return BadRequest(errorNoFile);
                }
            }
            section = await reader.ReadNextSectionAsync();
        }

        return Created("", uploadedFiles);
    }

when 2 or more files I get the exception on the second file on line:

var fileDto = await _zendeskManageFileService.UploadFileAsync(contentDisposition.FileName.ToString(), section.Body);

UploadFileAsync has the following code:

    public async Task<FileDto> UploadFileAsync(string filename, Stream stream)
    {
        if (stream.Position != 0)
            stream.Position = 0;

        var upload = await _zendeskClient.Attachments.UploadAsync(filename, stream);
        FileDto fileDto = new FileDto
        {
            Id = upload.Id,
            ContentType = upload.Attachment.ContentType,
            Token = upload.Token,
            ContentUrl = upload.Attachment.ContentUrl,
            FileName = upload.Attachment.FileName,
            Size = upload.Attachment.Size
        };

        return fileDto;
    }

    public async Task<Upload> UploadAsync(
        string fileName, 
        Stream inputStream, 
        string token = null,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        var attachmentResponse = await ExecuteRequest(async (client, canToken) => 
                await client.PostAsBinaryAsync(
                    UploadsResourceUri + $"?filename={fileName}&token={token}",
                    inputStream,
                    fileName,
                    canToken)
                .ConfigureAwait(false), 
                "UploadAsync",
                cancellationToken)
            .ThrowIfUnsuccessful("attachments#upload-files", HttpStatusCode.Created)
            .ReadContentAsAsync<UploadResponse>();

        return attachmentResponse.Upload;
    }

    public static async Task<HttpResponseMessage> PostAsBinaryAsync(
        this HttpClient client,
        string requestUri,
        Stream inputStream,
        string fileName,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        using (var stream = new MemoryStream())
        {
            inputStream.CopyTo(stream);

            using (var content = new ByteArrayContent(stream.ToArray()))
            {
                content.Headers.ContentType = new MediaTypeHeaderValue("application/binary");

                return await client.PostAsync(
                        requestUri, 
                        content, 
                        cancellationToken)
                    .ConfigureAwait(false);
            }
        }
    }

    internal static async Task<T> ReadContentAsAsync<T>(
        this Task<HttpResponseMessage> response,
        JsonConverter converter = null)
        where T : class
    {
        var unwrappedResponse = await response;

        if (unwrappedResponse == null)
            return null;

        if (converter == null)
        {
            return await unwrappedResponse
                .Content
                .ReadAsAsync<T>();
        }

        return await unwrappedResponse
            .Content
            .ReadAsAsync<T>(converter);
    }

    protected async Task<HttpResponseMessage> ExecuteRequest(
        Func<HttpClient, CancellationToken, Task<HttpResponseMessage>> requestBodyAccessor,
        string scope,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        using (LoggerScope(Logger, scope))
        using (var client = ApiClient.CreateClient())
        {
            return await requestBodyAccessor(client, cancellationToken);
        }
    }

I tried to create and use attribute to set AllowSynchronousIO = true

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AllowSynchronousIOAttribute : ActionFilterAttribute
{
    public AllowSynchronousIOAttribute()
    {
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
        if (syncIOFeature != null)
        {
            syncIOFeature.AllowSynchronousIO = true;
        }
    }
}

but it could not help. How to solve this problem for my case?

stack error is:

System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead. at Microsoft.AspNetCore.Server.IIS.Core.HttpRequestStream.Read(Byte[] buffer, Int32 offset, Int32 count) at Microsoft.AspNetCore.Server.IIS.Core.WrappingStream.Read(Byte[] buffer, Int32 offset, Int32 count) at Microsoft.AspNetCore.WebUtilities.BufferedReadStream.EnsureBuffered(Int32 minCount) at Microsoft.AspNetCore.WebUtilities.MultipartReaderStream.Read(Byte[] buffer, Int32 offset, Int32 count) at System.IO.Stream.CopyTo(Stream destination, Int32 bufferSize) at System.IO.Stream.CopyTo(Stream destination) at ZendeskApi.Client.Extensions.HttpRequestExtensions.PostAsBinaryAsync(HttpClient client, String requestUri, Stream inputStream, String fileName, CancellationToken cancellationToken) in C:\www\TMS\Libraries\ZendeskApi.Client\Extensions\HttpRequestExtensions.cs:line 75 at ZendeskApi.Client.Resources.AttachmentsResource.<>c__DisplayClass4_0.<b__0>d.MoveNext() in C:\www\TMS\Libraries\ZendeskApi.Client\Resources\AttachmentsResource.cs:line 45 --- End of stack trace from previous location where exception was thrown --- at ZendeskApi.Client.Resources.AbstractBaseResource1.ExecuteRequest(Func3 requestBodyAccessor, String scope, CancellationToken cancellationToken) in C:\www\TMS\Libraries\ZendeskApi.Client\Resources\AbstractBaseResource.cs:line 41 at ZendeskApi.Client.Extensions.HttpResponseExtensions.ThrowIfUnsuccessful(Task1 response, String helpDocLink, Nullable1 expected, String helpDocLinkPrefix) in C:\www\TMS\Libraries\ZendeskApi.Client\Extensions\HttpResponseExtensions.cs:line 90 at ZendeskApi.Client.Extensions.HttpResponseExtensions.ReadContentAsAsync[T](Task`1 response, JsonConverter converter) in C:\www\TMS\Libraries\ZendeskApi.Client\Extensions\HttpResponseExtensions.cs:line 45 at ZendeskApi.Client.Resources.AttachmentsResource.UploadAsync(String fileName, Stream inputStream, String token, CancellationToken cancellationToken) in C:\www\TMS\Libraries\ZendeskApi.Client\Resources\AttachmentsResource.cs:line 44 at Tms.ZendeskCore.Services.ZendeskManageFileService.UploadFileAsync(String filename, Stream stream) in C:\www\TMS\Libraries\Tms.ZendeskCore\Services\ZendeskManageFileService.cs:line 24 at Tms.WebApi.Controllers.Support.ManageFileApiController.Upload() in C:\www\TMS\Web\Tms.WebApi\Controllers\Support\ManageFileApiController.cs:line 86 at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Logged|12_1(ControllerActionInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS ======= Accept: / Accept-Encoding: gzip, deflate, br Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...7KlZaAycWieBAvkk2WUuTkI_UqNu9IzUhk0JAeLQkvU Cache-Control: no-cache Connection: keep-alive Content-Length: 114796 Content-Type: multipart/form-data; boundary=--------------------------271301571241092277677258 Host: localhost:44373 User-Agent: PostmanRuntime/7.28.3 Postman-Token: 4fd12960-9bae-45f5-8ae1-e4a5080b887b

Oleg Sh
  • 8,496
  • 17
  • 89
  • 159
  • 2
    what is the stack-trace? my *guess* would be that the `section.Body` stream is being consumed with the synchronous API; is there a pipelines API on `section`? could you buffer it? etc – Marc Gravell Sep 03 '21 at 11:10
  • @MarcGravell added – Oleg Sh Sep 03 '21 at 11:16
  • heh, "called it" :p OK; so: you're *either* going to need to do one of: a) use the pipelines API rather than `Stream`, b) buffer the data manually, or c) enable sync IO; is there an API for upload that accepts a pipe-reader, by any chance? – Marc Gravell Sep 03 '21 at 11:35

1 Answers1

3

inputStream.CopyTo(stream) copies the stream synchronously. You'll want to either use a StreamContent instead of ByteArrayContent, or copy the stream asynchronously (CopyToAsync).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810