Web API MultipartAsyn c Задача без результата - PullRequest
0 голосов
/ 29 мая 2020

Цель состоит в том, чтобы загрузить один файл на мой веб-сервер, а затем сохранить его в базе данных ms sql с помощью multipartcontent. Во время загрузки файла в клиенте (приложение WPF) должен отображаться индикатор выполнения. В следующем примере кода показана только загрузка в поток памяти (без подключения к базе данных).

Соединение от клиента к серверу работает, загрузка в MemoryStream на стороне сервера работает и получение процента на стороне клиента работает (раздел ContinueWith в моем примере кода). Проблема в том, что клиент не получает последний запрос CreateResponse - например, тайм-аут или потеря соединения, я не уверен, потому что я не получаю сообщение об ошибке / исключении. Клиент никогда не получает окончательный результат задачи.

WebApi:

public class AttachmentsController : ApiController
{
    [HttpPost]
    [Route("api/Attachments/Upload")]
    public async Task<HttpResponseMessage> Upload()
    {
        if (!Request.Content.IsMimeMultipartContent())
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);

        try
        {
            AttachmentUpload _resultAttachmentUpload = null;

            var _provider = new InMemoryMultipartFormDataStreamProvider();

            await Request.Content.ReadAsMultipartAsync(_provider)
                .ContinueWith(t =>
                {
                    if (t.IsFaulted || t.IsCanceled)
                    {
                        throw new HttpResponseException(
                            HttpStatusCode.InternalServerError);
                    }

                    return new AttachmentUpload()
                    {
                        FileName = _provider.Files[0].Headers.ContentDisposition
                            .FileName.Trim('\"'),
                        Size = _provider.Files[0].ReadAsStringAsync().Result
                            .Length / 1024
                    };
                });

            return Request.CreateResponse<AttachmentUpload>(HttpStatusCode.Accepted,
                _resultAttachmentUpload);
        }
        catch (Exception e)
        {
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError,
                e.ToString());
        }
    }
}

Клиент WPF UploadService.cs:

private async Task<Attachment> UploadAttachment(AttachmentUpload uploadData,
    string filePath)
{
    try
    {
        var _encoding = Encoding.UTF8;
        MultipartFormDataContent _multipartFormDataContent =
            new MultipartFormDataContent();

        _multipartFormDataContent.Add(new StreamContent(new MemoryStream(
            File.ReadAllBytes(filePath))), uploadData.FileName, uploadData.FileName);
        _multipartFormDataContent.Add(new StringContent(uploadData.Id.ToString()),
            "AttachmentId", "AttachmentId");
        _multipartFormDataContent.Add(new StringContent(
            uploadData.Upload.ToString(CultureInfo.CurrentCulture)), "AttachmentUpload",
            "AttachmentUpload");
        _multipartFormDataContent.Add(new StringContent(
            uploadData.DocumentId.ToString()), "DocumentId", "DocumentId");
        _multipartFormDataContent.Add(new StringContent(
            uploadData.User, _encoding), "User", "User");

        //ProgressMessageHandler is instantiate in ctor to show progressbar
        var _client = new HttpClient(ProgressMessageHandler);
        _client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("multipart/form-data"));
        _client.Timeout = TimeSpan.FromMinutes(5);

        var _requestUri = new Uri(BaseAddress + "api/Attachments/Upload");

        var _httpRequestTask = await _client.PostAsync(_requestUri,
            _multipartFormDataContent)
            .ContinueWith<AttachmentUpload>(request =>
            {
                var _httpResponse = request.Result;

                if (!_httpResponse.IsSuccessStatusCode)
                {
                    throw new Exception();
                }

                var _response = _httpResponse.Content.ReadAsStringAsync();

                AttachmentUpload _upload =
                    JsonConvert.DeserializeObject<AttachmentUpload>(_response.Result);

                return _upload;
            });

        var _resultAttachment = _httpRequestTask;

        return new Attachment()
        {
            Id = _resultAttachment.Id,
            FileName = _resultAttachment.FileName,
            Comment = _resultAttachment.Comment,
            Upload = _resultAttachment.Upload,
        };

    }
    catch (Exception e)
    {
        //Handle exceptions
        //file not found, access denied, no internet connection etc etc
        var tmp = e;
        throw e;
    }
}

В программе не хватает var _httpRequestTask = await _client.PostAsync(...). Отладчик никогда не достигает строки var _resultAttachment = _httpRequestTask;.

Большое спасибо за вашу помощь.

1 Ответ

2 голосов
/ 29 мая 2020

Прежде всего, не смешивайте await и ContinueWith, введение async / await фактически делает ContinueWith устаревшим.

Причина, по которой var _resultAttachment = _httpRequestTask; никогда не попадание вызвано тем, что вы создали тупик.

WPF имеет контекст синхронизации, который обеспечивает возобновление продолжения в потоке пользовательского интерфейса.

В строке AttachmentUpload _upload = JsonConvert.DeserializeObject<AttachmentUpload>(_response.Result);, _response.Result - это блокирующий вызов ; он блокирует текущий поток до тех пор, пока Task, на который ссылается _response, не завершится.

Метод ReadAsStringAsync, который сгенерировал Task, будет пытаться возобновить работу после завершения асинхронной работы, и контекст синхронизации WPF заставит его использовать поток пользовательского интерфейса, который был заблокирован _response.Result, отсюда и взаимоблокировка.

Чтобы исправить это, используйте ключевое слово await для каждые асинхронный вызов:

var _httpResponse = await _client.PostAsync(_requestUri, _multipartFormDataContent);

if (!_httpResponse.IsSuccessStatusCode)
{
    throw new Exception();
}

var _response = await _httpResponse.Content.ReadAsStringAsync();

var _resultAttachment = JsonConvert.DeserializeObject<AttachmentUpload>(_response);

Вы также должны заметить, что код гораздо более читается без ContinueWith.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...