.net 5 Azure Function 多部分文件上传问题

.net 5 Azure Function multipart file upload issue

我正在将 Azure 函数从 .net 3.1 升级到 5,但遇到了一些困难。许多事情从 3.1 到 5 发生了变化,我正在努力找出细微差别。我有一个接受 multipart/form-data post 的函数,它是这样创建的:

public class EmailService
    {
        private EmailPost _post;

        public EmailService() { }

        public EmailService(EmailPost post)
        {
            _post = post;
        }

        public EmailPost Post => _post;

        public async Task<EmailServiceResult> SendAsync()
        {
            try
            {
                var httpClient = new HttpClient();
                var form = new MultipartFormDataContent();
                    
                form.Add(new StringContent(string.Join(",", _post.To)), "To");
                form.Add(new StringContent(string.Join(",", _post.Cc)), "Cc");
                form.Add(new StringContent(string.Join(",", _post.Bcc)), "Bcc");
                form.Add(new StringContent(_post.From), "From");
                form.Add(new StringContent(_post.Subject), "Subject");
                form.Add(new StringContent(_post.Body), "Body");
                form.Add(new StringContent(_post.IsHtml.ToString()), "IsHtml");
                form.Add(new StringContent(_post.IsPriority.ToString()), "IsPriority");

                // any files?

                foreach (var i in _post.Files)
                {
                    if (!File.Exists(i))
                    {
                        throw new Exception("File does not exist. Make sure it's a full path.");
                    }

                    var filename = i.Substring(i.LastIndexOf("\", StringComparison.InvariantCultureIgnoreCase) + 1);
                    var fs = new FileStream(i, FileMode.Open, FileAccess.Read);
                    var fileByteArray = new byte[fs.Length];
                    fs.Read(fileByteArray, 0, fileByteArray.Length);
                    form.Add(new ByteArrayContent(fileByteArray, 0, fileByteArray.Length), "filename", filename);
                    fs.Close();
                }

                var endPointUrl = "my az function endpoint url";

                // if it's local, then use the test version
                if (Debugger.IsAttached)
                    endPointUrl = "local url here";

                var response = await httpClient.PostAsync(endPointUrl, form);

                response.EnsureSuccessStatusCode();
                httpClient.Dispose();
            }
            catch (Exception ex)
            {
                return new EmailServiceResult
                {
                    Success = false,
                    Message = ex.Message
                };
            }

            return new EmailServiceResult {Success = true};
        }
    }

然后是 Azure 函数:

    public class Email
    {
        private ILogger _log;
        private IConfiguration _config;

        public Email(IConfiguration config, ILogger<Email> logger)
        {
            _config = config;
            _log = logger;
        }

        [Function("Email")]
        public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = "email")]HttpRequestData req, FunctionContext context)
        {
            try
            {
                // Suck the request body into a stream
                var ms = new MemoryStream();
                await req.Body.CopyToAsync(ms);
                ms.Position = 0;

                // look through all the keys
                foreach (var h in req.Headers)
                    _log.LogInformation(h.Key + ":" + string.Join(',', h.Value));

                var streamContent = new StreamContent(ms);
                
                // This line was used with 3.1 but isn't available any more because the req object is different (HttpRequestData in v5 vs HttpRequest in v3)
                // streamContent.Headers.ContentType = req.Content.Headers.ContentType;
                // so I did it this way instead
                var ct = req.Headers.FirstOrDefault(o => o.Key == "Content-Type").Value.FirstOrDefault().Split(';');
                
                // this line is the kicker. it doesn't accept a value with a boundary, which is needed later
                // multipart/form-data; boundary="b0ff40b2-8b47-4746-b85b-a876924e4e4c"
                // so I just pass in: multipart/form-data
                var mthv = new System.Net.Http.Headers.MediaTypeHeaderValue(ct[0]);                
                streamContent.Headers.ContentType = mthv;
                
                // this line needs the boundary, which I can't figure out how to enter.
                var provider = await streamContent.ReadAsMultipartAsync();
                
                // ... more processing of fields              
                return req.CreateResponse(System.Net.HttpStatusCode.OK);
            }
            catch (Exception ex)
            {
               ///... error handling
            }
        }
    }

然后为了完整起见,这是我的程序文件:

public class Program
    {
        public static async Task Main(string[] args)
        {        
            var host = new HostBuilder()
                .ConfigureAppConfiguration(c =>
                {
                    c.SetBasePath(Environment.CurrentDirectory);
                    c.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true);
                    // Add Environment Variables since we need to get the App Configuration connection string from settings.
                    c.AddEnvironmentVariables();
                    // Call Build() so we can get values from the IConfiguration.
                    var config = c.Build();
                })
                .ConfigureFunctionsWorkerDefaults()
                .ConfigureServices(s =>
                {                     
                    // s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
                })
                .Build();

            await host.RunAsync();
        }

我很难弄清楚如何使用: var provider = await streamContent.ReadAsMultipartAsync(); 这需要边界但是: var mthv = new System.Net.Http.Headers.MediaTypeHeaderValue(ct[0]); 不允许边界。

我收到的错误是:提供的 'HttpContent' 实例无效。它没有 'multipart' content-type header 和 'boundary' 参数。 (参数'content')

如果有人对这个具体问题有一些建议,我将不胜感激。或者,如果有另一种方法可以完全做到这一点,那也很好。谢谢!

Media-type headers 由带有可选参数的 media-type 组成。 constructor for MediaTypeHeaderValue only takes a media-type; the parameters should be set via the Parameters property:

var mthv = new System.Net.Http.Headers.MediaTypeHeaderValue("multipart/form-data");
mthv.Parameters.Add(new NameValueHeaderValue("boundary", "b0ff40b2-8b47-4746-b85b-a876924e4e4c"));
streamContent.Headers.ContentType = mthv;

如果您需要解析传入请求的 boundary 值并使用相同的值,那么我建议您查看 MediaTypeHeaderValue.Parse.