У меня есть серверное приложение Blazor в качестве внешнего интерфейса и приложение-функция Azure в качестве внутреннего интерфейса. Пользователь будет загружать файл на сервер blazor, который напрямую пересылает файл API функции Azure.
Моя проблема в том, что я не могу перенаправить предоставленный поток в HttpClient, который вызывает приложение-функцию Azure. Что касается функции Azure, я получаю в качестве «тела» NullStream, который не имеет данных.
У меня следующая архитектура системы. Вся база кода находится в dotnet 8.
Пользователь может загружать файлы в серверное приложение blazor. Это работает так, как ожидалось. Форм-файлы принимаются корректно и поток корректно десериализуется.
Компонент пользовательского интерфейса Blazor
@inject IApiClient ApiClient;
<div style = "width: 100%; display: flex; align-items: center; justify-content: center;">
<label for = "inputFile" class = "fileUpload">
<InputFile id = "inputFile" OnChange = "@LoadFiles" class = "d-none" />
</label>
</div>
@code {
private long _maxFileSize = 10 * 1024 * 1024;
private int _maxAllowedFiles = 1;
private async Task LoadFiles(InputFileChangeEventArgs e)
{
foreach (var file in e.GetMultipleFiles(_maxAllowedFiles))
{
try
{
var fileStream = file.OpenReadStream(_maxFileSize);
var fileMetaData = await ApiClient.UploadFileAsync(fileStream, file.ContentType, file.Name);
// omitted
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
Сервисная функция, вызывающая серверную часть API Я не хочу загружать весь поток в память, а хочу напрямую предоставить его как содержимое потока в составные данные формы.
public async Task<FileMetaDataDto> UploadFileAsync(Stream data, string contentType, string fileName)
{
var content = new MultipartFormDataContent();
content.Add(new StreamContent(data), fileName);
// this would copy the whole stream into memory
// await content.ReadAsStringAsync();
var result = await _httpClient.PostAsync("api/v1/files", content);
var json = await result.Content.ReadAsStringAsync();
var deserialized = JsonSerializer.Deserialize<FileMetaDataDto>(json, SerializerExtensions.DefaultOptions());
return deserialized ?? new FileMetaDataDto();
}
Функция загрузки — это HttpTrigger. Сама функция изолирована от модели версии 4.0. В качестве внешней зависимости я использую следующий пакет:
<PackageReference Include="HttpMultipartParser" Version="5.0.0" />
[Function("UploadFile")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = "v1/files")] HttpRequestData req)
{
try
{
var body = req.Body;
var parsedFormBody = MultipartFormDataParser.ParseAsync(req.Body);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return req.CreateResponse(HttpStatusCode.OK);
}
При использовании API напрямую с почтальоном или Swagger API функций Azure работает правильно. Но я также заметил, что оба сначала загружают файлы в память и отправляют все данные сразу. В этом случае req.Body имеет тип MemoryStream.
То же самое работает, если я сначала загружаю весь предоставленный пользователем файл в память (например, копирование в MemoryStream или вызов «await content.ReadAsStringAsync();»
Как обрабатывать загрузку файлов (данные формы) с помощью функции Azure .NET5 и HttpRequestData? Моей отправной точкой был этот вопрос, но он не решил проблему.
Функции Azure — Как читать данные формы Устаревший
Проблема с загрузкой составного файла в .net 5 Azure Function Полностью загружает файл в память во внешнем интерфейсе.
Я не уверен, где находится ошибка
1- Блазор-сервер. Правильно ли я использую Httpclient вместе с данными формы Multipart? Опять же, я не хочу загружать весь поток в память в серверном приложении blazor.
2- функция Azure Это правильный способ получить данные формы?
Я также загрузил файл из Postman в Function app.





Я создал серверное приложение Blazor в качестве внешнего интерфейса и приложение-функцию Azure в качестве серверной части для загрузки файла из серверного приложения Blazor в приложение-функцию.
Код:
внешний интерфейс :
FileUploader.razor:
@page "/file-uploader"
@using BlazorApp1.Services
@inject IApiClient ApiClient
<div style = "width: 100%; display: flex; align-items: center; justify-content: center; flex-direction: column;">
<label for = "inputFile" class = "fileUpload">
<InputFile id = "inputFile" OnChange = "@OnInputFileChange" />
</label>
<button @onclick = "UploadFile" disabled = "@(!isFileSelected)">Upload</button>
</div>
@code {
private IBrowserFile selectedFile;
private FileMetaDataDto fileMetaData;
private bool isFileSelected = false;
private long _maxFileSize = 10 * 1024 * 1024;
private int _maxAllowedFiles = 1;
private void OnInputFileChange(InputFileChangeEventArgs e)
{
if (e.FileCount > 0)
{
selectedFile = e.GetMultipleFiles(_maxAllowedFiles).FirstOrDefault();
isFileSelected = selectedFile != null;
}
}
private async Task UploadFile()
{
if (selectedFile != null)
{
try
{
var fileStream = selectedFile.OpenReadStream(_maxFileSize);
fileMetaData = await ApiClient.UploadFileAsync(fileStream, selectedFile.ContentType, selectedFile.Name);
}
catch (Exception ex)
{
Console.WriteLine($"Error uploading file: {ex.Message}");
}
}
}
}
АпиКлиент.cs:
using System.Text.Json;
namespace BlazorApp1.Services
{
public class ApiClient : IApiClient
{
private readonly HttpClient _httpClient;
public ApiClient(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task<FileMetaDataDto> UploadFileAsync(Stream data, string contentType, string fileName)
{
var content = new MultipartFormDataContent();
content.Add(new StreamContent(data), "file", fileName);
var result = await _httpClient.PostAsync("api/v1/files", content);
result.EnsureSuccessStatusCode();
var json = await result.Content.ReadAsStringAsync();
var deserialized = JsonSerializer.Deserialize<FileMetaDataDto>(json, SerializerExtensions.DefaultOptions());
return deserialized ?? new FileMetaDataDto();
}
}
}
Бэкэнд:
ЗагрузитьФайлФункция.cs:
using System.Net;
using System.Text.Json;
using HttpMultipartParser;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
public class UploadFileFunction
{
private readonly ILogger<UploadFileFunction> _logger;
private readonly string _uploadFolderPath;
public UploadFileFunction(ILogger<UploadFileFunction> logger)
{
_logger = logger;
_uploadFolderPath = Path.Combine(Environment.CurrentDirectory, "UploadedFiles");
if (!Directory.Exists(_uploadFolderPath))
{
Directory.CreateDirectory(_uploadFolderPath);
}
}
[Function("UploadFileFunction")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = "v1/files")] HttpRequestData req)
{
var response = req.CreateResponse(HttpStatusCode.OK);
try
{
var parsedFormBody = await MultipartFormDataParser.ParseAsync(req.Body);
var file = parsedFormBody.Files.FirstOrDefault();
if (file != null)
{
var fileName = file.FileName;
var fileStream = file.Data;
var filePath = Path.Combine(_uploadFolderPath, fileName);
using (var fileStreamDestination = File.Create(filePath))
{
await fileStream.CopyToAsync(fileStreamDestination);
}
_logger.LogInformation($"File {fileName} uploaded successfully at {filePath}");
var fileMetaData = new FileMetaDataDto
{
FileName = fileName,
FileSize = fileStream.Length
};
var jsonResponse = JsonSerializer.Serialize(fileMetaData);
response.Headers.Add("Content-Type", "application/json");
await response.WriteStringAsync(jsonResponse);
}
else
{
_logger.LogWarning("No file uploaded.");
response.StatusCode = HttpStatusCode.BadRequest;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing file upload");
response.StatusCode = HttpStatusCode.InternalServerError;
}
return response;
}
}
public class FileMetaDataDto
{
public string FileName { get; set; }
public long FileSize { get; set; }
}
Вывод браузера:
Я загрузил файл из серверного приложения Blazor в приложение Function в браузере, как показано ниже:


Почтальон:
Я загрузил файл в приложение-функцию из Почтальона, как показано ниже:

Вывод приложения-функции:
Я успешно загрузил файлы в приложение-функцию как из пользовательского интерфейса серверного приложения Blazor, так и из Postman.

Вот успешно сохраненные файлы в каталоге приложения-функции, как показано ниже:

Я загрузил файл с помощью приложения Blazor-server в функциональное приложение.