.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
.
我正在将 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
.