Doug Dawson
Doug Dawson

Reputation: 1273

WebAPI error reading MIME multipart body part

My team recently refactored a Web API service to move some of the repetitive code into static methods. One method is related to extracting an uploaded file from the request. The method works in unit testing, but under load, is throwing exceptions. Part of the code was found in an SO post, but I'm concerned that, overall, we're not using it correctly. Here's the code:

internal static string ExtractFile(HttpRequestMessage request)
{
    if (request.Content.IsMimeMultipartContent())
    {
        string uploadRoot = ServiceHelper.GetUploadDirectoryPath();

        var provider = new MultipartFormDataStreamProvider(uploadRoot);

        try
        {
            Task.Factory
            .StartNew(() => provider = request.Content.ReadAsMultipartAsync(provider).Result,
                CancellationToken.None,
                TaskCreationOptions.LongRunning, // guarantees separate thread
                TaskScheduler.Default)
            .Wait();
        }
        catch(System.AggregateException ae)
        {
            if(log.IsErrorEnabled)
            {
                foreach(var ex in ae.InnerExceptions)
                {
                    log.Error("ReadAsMultipartAsync task error.", ex);
                }
            }

            var errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, "An error occurred while extracting the uploaded file from the request.");
            throw new HttpResponseException(errorResponse);
        }

        var fileData = provider.FileData.First();

        var localName = fileData.LocalFileName;
        var content = File.ReadAllText(localName);

        if (log.IsDebugEnabled)
        {
            var embeddedName = fileData.Headers.ContentDisposition.FileName;
            log.DebugFormat("File {0} was successfully uploaded as '{1}'.", embeddedName, localName);
        }

        return content;
    }
    else
    {
        log.Error("Invalid request received. Request must be in a multipart/form-data request.");

        var errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Request must be a multipart/form-data request and contain one file.");
        throw new HttpResponseException(errorResponse);
    }
}

Walking thru the logs, I see errors like these:

System.IO.IOException: Error reading MIME multipart body part. ---> System.IO.IOException ---> System.Net.HttpListenerException: The I/O operation has been aborted because of either a thread exit or an application request

HttpListenerRequest disposed

System.IO.IOException: Error reading MIME multipart body part. ---> System.IO.IOException ---> System.Net.HttpListenerException: An operation was attempted on a nonexistent network connection

This web service is running as a self-hosted OWIN Windows service. The file uploads are small (3k to 4k).

I can't recreate the issue with a single upload. The client that is talking to the service uses tasks to post files, but it doesn't usually run more than 4 or 5 tasks concurrently. My team and I are relatively new to .NET tasks. One of the developers is wondering if the TaskCreationOptions.LongRunning parameter is actually hurting more than it helps. Any suggestions?

Update:

I tried switching out the Task.Factory code with this:

var task = Task.Run(async () => await request.Content.ReadAsMultipartAsync(provider));
task.Wait();
provider = task.Result;

I'm still having some issues, but this seems to work better. Not sure why, though.

Upvotes: 6

Views: 21495

Answers (3)

Khateeb321
Khateeb321

Reputation: 2104

I had the same issue, it was resolved by adding following code in web.config

<system.web>
    <httpRuntime maxRequestLength="30000000" />
</system.web>

<system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="30000000" />
      </requestFiltering>
    </security>
</system.webServer>

Source: http://stackoverflow.com/questions/20942636/webapi-cannot-parse-multipart-form-data-post

Upvotes: 27

Doug Dawson
Doug Dawson

Reputation: 1273

While analyzing the logs, I realized that our client code was being extremely aggressive with how many threads it was spawning. I added a throttle to the client to slow things down some and the server liked that much better. I believe my original server code change had little or no effect. The difference in the output was likely due to other variables like network latency.

Here's how I updated the client code:

// New private field in my client class
private SemaphoreSlim _semaphore = new SemaphoreSlim(Settings.Default.MaxConcurrentRequests, Settings.Default.MaxConcurrentRequests);

// Added continuation to Polly tasks
foreach (var file in files)
{
    var task = retryPolicy.ExecuteAsync(() => SendIllustrationRequest(file));
    task.ContinueWith((x) => _semaphore.Release());

    _transactionTasks.Add(task);
}

Task.WaitAll(_transactionTasks.ToArray());

// Added semaphore wait method to the start of the SendIllustrationRequest(file) method
_semaphore.Wait();

This technique worked well for me since I am using Polly and Polly starts to execute a task immediately. If you have the option to create tasks without immediately starting them, there are other options.

Upvotes: 1

Igor
Igor

Reputation: 62213

Let me state that this is not the answer as to why your code is failing but a question I have that is easier asked with some room to show some formatted code. I will/can remove this as answer if this does not provide any help/direction.

I am assuming your method is being called from an incoming web api request. Is there any reason you are not making this "async all the way"? This is the simplest way to structure it and has the least chance of deadlocking later due to poor/defective code (which really easy to do if you are just starting off with Tasks and async/await).

// new signature (I changed the name and prefixed Async which is a common convention)
internal static async Task<string> ExtractFileAsync(HttpRequestMessage request)
{
    if (request.Content.IsMimeMultipartContent())
    {
        string uploadRoot = ServiceHelper.GetUploadDirectoryPath();

        var provider = new MultipartFormDataStreamProvider(uploadRoot);

        await request.Content.ReadAsMultipartAsync(provider);
        /* rest of code */
    }
}

// calling method example from some Web API Controller
public async Task<IHttpActionResult> Post(CancellationToken token)
{ 
    var result = await ExtractFileAsync(Request).ConfigureAwait(true); 
    /*some other code*/
}

Upvotes: 0

Related Questions