ارسال فایل ها و داده های اضافی با استفاده از HttpClient در NET Core
چگونه با استفاده از HttpClient در NET Core درخواست های HTTP چند بخشی(multipart) حاوی فایل ها را ارسال کنیم؟ اگر endpoint به مقداری داده اضافی نیاز دارد چه باید کرد؟ این مقاله راهحلهایی را برای موارد رایج توضیح میدهد و پیامهای HTTP خامی را که توسط HttpClient برای درخواستهای multipart تولید میشوند، ارائه میکند.
ارسال یک فایل
فرض کنید ما یک برنامه .NET Core داریم که روی localhost:5001 اجرا می شود و یک endpoint مانند زیر تعریف شده است. برای سادگی، کد وضعیت 200 را برمی گردانیم.
[ApiController]
[Route("file")]
public class FileController : ControllerBase
{
[HttpPost]
public IActionResult Upload([FromForm] IFormFile file)
{
// code responsible for file processing
return Ok();
}
}
خوب، اکنون می خواهیم با استفاده از HttpClient از یک برنامه دیگر درخواستی به این نقطه پایانی ارسال کنیم. از آنجایی که فایل آرگومان نقطه پایانی با ویژگی FromForm تزئین شده است، انتظار یک نوع محتوای multipart/form-data را دارد.
ابتدا HttpClient را مقداردهی اولیه می کنیم. توجه داشته باشید که در زندگی واقعی، ایجاد HttpClient در هر درخواست، تمرین خوبی نیست. این به دلیل مشکل تخلیه سوکت است. با این حال، از حوصله این مقاله خارج است.
خود درخواست توسط شی HttpRequestMessage و MultipartFormDataContent متصل به آن تعریف می شود. MultipartFormDataContent حاوی یک جریان فایل واحد است که می خواهیم ارسال کنیم. "فایل" نام یک آرگومان با نوع IFrmFile است که توسط نقطه پایانی هدف 🌐 لازم است.
private static async Task UploadSampleFile()
{
var client = new HttpClient
{
BaseAddress = new("https://localhost:5001")
};
await using var stream = System.IO.File.OpenRead("./Test.txt");
using var request = new HttpRequestMessage(HttpMethod.Post, "file");
using var content = new MultipartFormDataContent
{
{ new StreamContent(stream), "file", "Test.txt" }
};
request.Content = content;
await client.SendAsync(request);
}
ارسال فایل با اطلاعات اضافی
اگر نقطه پایانی هدف یک فایل واحد و چند فیلد اضافی را بپذیرد چه باید کرد؟
[ApiController]
[Route("file")]
public class FileController : ControllerBase
{
[HttpPost]
public IActionResult Upload([FromForm] FileDataDto dto)
{
// code responsible for file processing
return Ok();
}
}
public class FileDataDto
{
public IFormFile FileToUpload1 { get; set; }
public DataDto Data { get; set; }
}
public class DataDto
{
public string Name { get; set; }
public string[] Tags { get; set; }
public ChildDataDto ChildData { get; set; }
}
public class ChildDataDto
{
public string Description { get; set; }
}
نکته کلیدی در سمت کلاینت این است که یک ابجکت request تهیه کنید که به درستی توسط بایندر مدل به FileDataDto نگاشت شود. باز هم، چون DTO نقطه پایانی با ویژگی FromForm تزئین شده است، انتظار درخواست multipart را دارد.
این بار MultipartFormDataContent شامل collectionیی از اشیاء HttpContent است. ما StreamContent حاوی جریان فایل و چندین شی از نوع StringContent را مشخص می کنیم.
هر شی StringContent یک ویژگی واحد را تعریف می کند که در نقطه پایانی هدف به DataDto نگاشت می شود. به عنوان مثال، برای پر کردن ویژگی Name در DataDto، باید کل مسیر این ویژگی را به عنوان نام محتوا مشخص کنیم. ویژگی های مجموعه مانند برچسب ها را می توان با چندین شیء StringContent با یک نام پر کرد.
private static async Task UploadSampleFile()
{
var client = new HttpClient
{
BaseAddress = new("https://localhost:5001")
};
await using var stream = System.IO.File.OpenRead("./Test.txt");
var payload = new
{
Name = "payload name",
Tags = new[] { "tag1", "tag2" },
ChildData = new
{
Description = "test description"
}
};
using var request = new HttpRequestMessage(HttpMethod.Post, "file");
using var content = new MultipartFormDataContent
{
// file
{ new StreamContent(stream), "FileToUpload1", "Test.txt" },
// payload
{ new StringContent(payload.Name), "Data.Name" },
{ new StringContent(payload.Tags[0]), "Data.Tags" },
{ new StringContent(payload.Tags[1]), "Data.Tags" },
{ new StringContent(payload.ChildData.Description), "Data.ChildData.Description" }
};
request.Content = content;
await client.SendAsync(request);
}
مثال بیشتر
در مثالهای بالا ما فایل رو از جایی میخوندیم. حالا اگر من در cotrollerم اکشنی داشته باشم که بهش فایل ارسال بشه و من بخوام بدون ذخیره کردنش, مستقیم ارسالش کنم به یه api بیرونی چطوری انجامش بدم؟
کد زیر همون api بیرونی هست
[HttpPost("[action]")]
public async Task<IActionResult> UploadIdPhotos(long rqId, IFormFile idCardFront, IFormFile idCardBack)
{
return Ok();
}
نحوه کال کردن از داخل اکشن controller خودمون. به قسمت Request.Form.Files[0].OpenReadStream() دقت کنید
var _httpClient = new HttpClient{BaseAddress = new Uri("https://localhost:7098/api/")};
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "......");
var multipartFormContent = new MultipartFormDataContent();
//Add other fields
multipartFormContent.Add(new StringContent(rq.Id.ToString()), name: "rqId");
//Add the file 1
var fileStreamContent = new StreamContent(Request.Form.Files[0].OpenReadStream());
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
multipartFormContent.Add(fileStreamContent, name: "idCardFront", fileName: "id1.png");
//Add the file 2
var fileStreamContent2 = new StreamContent(Request.Form.Files[1].OpenReadStream());
fileStreamContent2.Headers.ContentType = new MediaTypeHeaderValue("image/png");
multipartFormContent.Add(fileStreamContent2, name: "idCardBack", fileName: "id2.png");
//Send it
res = await _httpClient.PostAsync($"UploadIdPhotos?rqId={rq.Id}", multipartFormContent);
var reponse = await res.Content.ReadAsStringAsync();
یذره سختترش کنیم. اگر بجای 2 فایل 2 لیست فایل داشته باشیم چطور میشه کد؟
[HttpPost("[action]")]
public async Task<IActionResult> UploadChequePhotos(long rqId, IFormFileCollection col1, IFormFileCollection col2)
{
return Ok();
}
نحوه کال کردن
var _httpClient = new HttpClient{BaseAddress = new Uri("https://localhost:7098/api/")};
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "......");
var multipartFormContent = new MultipartFormDataContent();
//Add other fields
multipartFormContent.Add(new StringContent(rq.Id.ToString()), name: "rqId");
//Add the file
for (int i = 0; i < Request.Form.Files.Count; i++)
{
var fileStreamContent = new StreamContent(Request.Form.Files[i].OpenReadStream());
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
if (Request.Form.Files[i].Name.StartsWith("chk"))
{
multipartFormContent.Add(fileStreamContent, name: "col1", fileName: $"id{i}.png");
}
else
{
multipartFormContent.Add(fileStreamContent, name: "col2", fileName: $"id{i}.png");
}
}
//Send it
res = await _httpClient.PostAsync($"UploadChequePhotos?rqId={rq.Id}", multipartFormContent);
var a = await res.Content.ReadAsStringAsync();