Как вывести на экран результат отправки файлов в процентах С#?

Я отправляю файлы по http запросу, в моем методе SendFile(). Туда я передаю ссылку куда отправлять файлы и сами файлы. Создаю объект типа HttpClient();. Создаю HttpRequestMessage() в который помещаю свой content. На сервере настроена авторизация пользователей и каждому дается токен, поэтому в заголовках к запросу я так же указываю этот токен. Когда полностью запрос сформирован я вызываю метод client.SendAsync().Result. В зависимости от размера файла этот метод с течением определенного времени возвращает ответ. Но я не могу отследить этот момент, что бы вывести информацию для пользователя, о процессе отправки файла. Вот код метода:

private  void _SendFile(string ShortUrl,  ObservableCollection<LastFilesModel> files)
{
    try
    {
        //Полная ссылка 
        var url = IpAddress_server + ShortUrl;
                    
        client = new HttpClient();
        var content = new MultipartFormDataContent();
        content.Headers.ContentType.MediaType = "multipart/form-data";
                  
        int count = 0;
        string key = "";
        foreach(var file in files)
        {
            count++;
            var stream = new System.IO.FileStream(file.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
                    
            key = "file_" + count;
            content.Add(new StreamContent(stream), key, file.ShortName);
        }
                  
        // Тип Запроса
        var httpMethod = HttpMethod.Post;
        // Добавление данных в запрос 
        var request = new HttpRequestMessage()
        {
            RequestUri = new Uri(url),
            Method = httpMethod,
            Content = content,
        };
                     
        // Токен который юзер получит при авторизации на сервере    
        request.Headers.Add("Authorization", "Bearer " + MyUser.Token);
        // Отправка и ожидание ответа
        var response = client.SendAsync(request).Result;
        response.EnsureSuccessStatusCode();
    }
    catch(Exception ex)
    {
        var s =ex.Message;
    }
}

Когда срабатывает метод client.SendAsync() то переменная response будет ожидать ответа, программа не зависает, просто спустя какое то время эта переменная будет содержать ответ в виде Json строки. Помогите понять как вместо ожидания ответа сделать прогресс отправки, в виде процентов или подобного.

Команда:

public ICommand SendMessage
{
    get
    {
        return new Command(async (object obj) =>
        {
            //.... там выше код который отправлял сообщение на данный момент он лишний.
            if(AttachFileCollection.Count != 0)
            {
                // Пользователь 
                var user = await UserDbService.GetUser();
                // отправка файла      
                SendFile("/api/message/upload/chatroom/"+Room_id+"/user/"+ user.Id, AttachFileCollection);
                // Настройки на экране 
                FullSize = 0;
                IsVisibleAttachFile = false;
                AttachFileCollection.Clear();
            }
        });
    }
} // SendMessage

Вызов приватного метода

 public void SendFile(string ShortUrl, ObservableCollection<LastFilesModel> files)
        {
            _SendFile(ShortUrl, files);

        }

Ответы (1 шт):

Автор решения: aepot

Для отчетов о загрузке файлов на сервер я создал такой HttpContent.
(код не проверял, писал на коленке)

public class ProgessFileStreamContent : HttpContent
{
    private const int bufferSize = 8192;

    private readonly string _path;
    private readonly Action<int> _callback;
    private readonly long _length;
    private readonly CancellationTokenSource _cts;

    public ProgessFileStreamContent(string path, Action<int> callback)
    {
        _path = path;
        _callback = callback;
        _length = new FileInfo(path).Length;
        _cts = new CancellationTokenSource();
    }

    protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        if (_cts.IsCancellationRequested)
            throw new ObjectDisposedException(nameof(ProgessFileStreamContent));
        long position = 0;
        int oldProgress = -1;
        byte[] buffer = new byte[bufferSize];
        using var fs = File.OpenRead(_path);
        int bytesRead = 0;
        while ((bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length, _cts.Token)) > 0)
        {
            await stream.WriteAsync(buffer, 0, bytesRead, _cts.Token);
            position += bytesRead;
            int progress = (int)(position * 100 / _length);
            if (progress != oldProgress)
            {
                oldProgress = progress;
                _callback(progress);
            }
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _cts.Cancel();
            _cts.Dispose();
        }
        base.Dispose(disposing);
    }

    protected override bool TryComputeLength(out long length)
    {
        length = _length;
        return true;
    }
}

Чтобы показать, как его использовать, написал вот такой класс

public class LastFilesModel
{
    public string ShortName { get; set; }
    public string FullName { get; set; }
}

public class HttpUploader
{
    private static readonly HttpClient client = new HttpClient();
    public string IpAddress_server { get; set; }

    public async Task<string> UploadFilesAsync(string shortUrl, IEnumerable<LastFilesModel> pathNames, IProgress<int> totalProgress)
    {
        LastFilesModel[] files = pathNames.ToArray();
        string url = IpAddress_server + shortUrl;
        using var content = new MultipartFormDataContent();
        int[] progressData = new int[files.Length];
        int oldProgress = -1;
        for (int i = 0; i < files.Length; i++)
        {
            int index = i;
            Action<int> progress = x => 
            { 
                progressData[index] = x;
                int p = progressData.Sum() / progressData.Length;
                if (oldProgress != p)
                {
                    oldProgress = p;
                    totalProgress.Report(p);
                }
            };
            LastFilesModel file = files[i];
            content.Add(new ProgessFileStreamContent(file.FullName, progress), $"file_{i + 1}", file.ShortName);
        }
        using var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
        using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
            
        return await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync().ConfigureAwait(false);
    }
}

Не обрабатывайте исключения в глубинах логики, когда надо прервать выполнение при сбоях. Делайте это на верхнем уровне.

Команду кстати при каждом вызове инициализируете, немного переделаю

private readonly _uploader = new HttpUploader();

public ICommand SendMessage { get; } = new Command(async (object obj) =>
{
    try
    {
        //.... 
        if (AttachFileCollection.Count != 0)
        {
            var user = await UserDbService.GetUser();
            IProgress<int> progress = new Progress<int>(UpdateProgress);
            await _uploader.UploadFilesAsync("/api/message/upload/chatroom/"+Room_id+"/user/"+ user.Id, AttachFileCollection, progress);
           // Настройки на экране 
            FullSize = 0;
            IsVisibleAttachFile = false;
            AttachFileCollection.Clear();
        }
    }
    catch (Exception ex)
    {
        // ...ex.Message
    }
});

private void UpdateProgress(int progress)
{
    // здесь обновляйте UI
}

Progress<T> имеет особенность, при которой его делегат всегда будет вызван в том же контексте синхронизации, в котором он сам был создан, в данном случае - в UI потоке.

→ Ссылка