Pass byte array from WPF to WebApi

0

tl;dr What is the best way to pass binary data (up to 1MBish) from a WPF application to a WebAPI service method?


I'm currently trying to pass binary data from a WPF application to a WebAPI web service, with variable results. Small files (< 100k) generally work fine, but any larger and the odds of success reduce.

A standard OpenFileDialog, and then File.ReadAllBytes pass the byte[] parameter into the client method in WPF. This always succeeds, and I then post the data to WebAPI via a PostAsync call and a ByteArrayContent parameter.

Is this the correct way to do this? I started off with a PostJSONAsync call, and passed the byte[] into that, but thought the ByteArrayContent seemed more appropriate, but neither work reliably.

Client Method in WPF

public static async Task<bool> UploadFirmwareMCU(int productTestId, byte[] mcuFirmware)
{
    string url = string.Format("productTest/{0}/mcuFirmware", productTestId);

    ByteArrayContent bytesContent = new ByteArrayContent(mcuFirmware);
    HttpResponseMessage response = await GetClient().PostAsync(url, bytesContent);

    ....
}

WebAPI Method

[HttpPost]
[Route("api/productTest/{productTestId}/mcuFirmware")]
public async Task<bool> UploadMcuFirmware(int productTestId)
{
    bool result = false;

    try
    {
        Byte[] mcuFirmwareBytes = await Request.Content.ReadAsByteArrayAsync();

        ....
    }

Web Config Settings

AFAIK these limits in web.config should be sufficient to allow 1MB files through to the service?

  <security>
    <requestFiltering>
    <requestLimits maxAllowedContentLength="1073741824" />
    </requestFiltering>
  </security>

<httpRuntime targetFramework="4.5" maxRequestLength="2097152"/>

I receive errors in WebAPI when calling ReadAsByteArrayAsync(). These vary, possibly due to the app pool in IIS Express having crashed / getting into a bad state, but they include the following (None of which have lead to any promising leads via google):

Specified argument was out of the range of valid values. Parameter name: offset
at System.Web.HttpInputStream.Seek(Int64 offset, SeekOrigin origin)\r\n   
at System.Web.HttpInputStream.set_Position(Int64 value)\r\n   at System.Web.Http.WebHost.SeekableBufferedRequestStream.SwapToSeekableStream()\r\n   at System.Web.Http.WebHost.Seek

OR

Message = "An error occurred while communicating with the remote host. The error code is 0x800703E5."

InnerException = {"Overlapped I/O operation is in progress. (Exception from HRESULT: 0x800703E5)"}

at System.Web.Hosting.IIS7WorkerRequest.RaiseCommunicationError(Int32 result, Boolean throwOnDisconnect)\r\n   
at System.Web.Hosting.IIS7WorkerRequest.ReadEntityCoreSync(Byte[] buffer, Int32 offset, Int32 size)\r\n   
at System.Web.Hosting.IIS7WorkerRequ...

Initially I thought this was most likely down to IIS Express limitations (running on Windows 7 on my dev pc) but we've had the same issues on a staging server running Server 2012.

Any advice on how I might get this working would be great, or even just a basic example of uploading files to WebAPI from WPF would be great, as most of the code I've found out there relates to uploading files from multipart forms web pages.

Many thanks in advance for any help.

wpf
asp.net-web-api
asked on Stack Overflow Oct 12, 2015 by Ted • edited Oct 13, 2015 by Ted

1 Answer

0

tl;dr It was a separate part of our code in the WebApi service that was causing it to go wrong, duh!


Ah, well, this is embarrassing.

It turns out our problem was down to a Request Logger class we'd registered in WebApiConfig.Register(HttpConfiguration config), and that I'd forgotten about.

It was reading the request content via async as StringContent, and then attempting to log it to the database in an ncarchar(max) field. This itself is probably OK, but I'm guessing all the weird problems started occurring when the LoggingHandler as well as the main WebApi controller, were both trying to access the Request content via async?

Removing the LoggingHandler fixed the problem immediately, and we're now able to upload files of up to 100MB without any problems. To fix it more permanently, I guess I rewrite of the LoggingHandler is required to set a limit on the maximum content size it tries to log / to ignore certain content types.

It's doubtful, but I hope this may be of use for someone one day!

public class LoggingHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        LogRequest(request);

        return base.SendAsync(request, cancellationToken).ContinueWith(task =>
        {
            var response = task.Result;

            // ToDo: Decide if/when we need to log responses
            // LogResponse(response);

            return response;
        }, cancellationToken);
    }

    private void LogRequest(HttpRequestMessage request)
    {
        (request.Content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
        {
            try
            {
                var callerId = CallerId(request);
                var callerName = CallerName(request);

                // Log request
                LogEntry logEntry = new LogEntry
                {
                    TimeStamp = DateTime.Now,
                    HttpVerb = request.Method.ToString(),
                    Uri = request.RequestUri.ToString(),
                    CorrelationId = request.GetCorrelationId(),
                    CallerId = callerId,
                    CallerName = callerName,
                    Controller = ControllerName(request),
                    Header = request.Headers.ToString(),
                    Body = x.Result
                };

                ...........
answered on Stack Overflow Oct 13, 2015 by Ted

User contributions licensed under CC BY-SA 3.0