From 8d77c866e905e810081f90e8a421c93689775ba1 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Mon, 7 Sep 2026 13:28:54 +0200 Subject: [PATCH] Multipart form-data with HttpClient: retarget net10.0, modernise the test server, cover JsonContent - Both projects retargeted net7.0 -> net10.0. - Tests packages lifted: Microsoft.AspNetCore.Mvc.Testing 10.0.11, Microsoft.NET.Test.Sdk 18.9.0, xunit 2.9.3, xunit.runner.visualstudio 4.0.0, coverlet.collector 10.0.1. - TestServer/Program.cs rewritten from an explicit Program class with a private static Main to top-level statements, plus a public partial class Program declaration so WebApplicationFactory in the test project still has a nameable type. - Controller namespace TestServer1.Controllers corrected to a file-scoped TestServer.Controllers; the trailing 1 was a leftover from a rename. - The three tests asserted by hand with if (!response.IsSuccessStatusCode) Assert.Fail(...); each now calls response.EnsureSuccessStatusCode(), which is one line and reports the actual status code on failure. - The file path $".{Path.DirectorySeparatorChar}john_doe.jpg" simplified to "john_doe.jpg"; the csproj already copies the file to the output directory, which is the working directory under the test runner. - New test covering a JsonContent part: JsonContent.Create sets application/json; charset=utf-8 on the part itself, and the part goes into the same multipart body as the StringContent fields. --- .../TestServer/Controllers/TestController.cs | 45 +++++++++---------- .../TestServer/Program.cs | 24 +++++----- .../TestServer/TestServer.csproj | 2 +- .../Tests/MultipartFormDataTests.cs | 41 +++++++++++------ .../Tests/Tests.csproj | 12 ++--- 5 files changed, 67 insertions(+), 57 deletions(-) diff --git a/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/Controllers/TestController.cs b/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/Controllers/TestController.cs index b505dee9cd..d86d1c45f3 100644 --- a/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/Controllers/TestController.cs +++ b/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/Controllers/TestController.cs @@ -1,35 +1,34 @@ using Microsoft.AspNetCore.Mvc; using System.Net.Mime; -namespace TestServer1.Controllers +namespace TestServer.Controllers; + +[ApiController] +public class TestController : ControllerBase { - [ApiController] - public class TestController : ControllerBase + [HttpPost("upload-form")] + public IActionResult Post() { - [HttpPost("upload-form")] - public IActionResult Post() + if (Request.Form.ContainsKey("first_name") && + Request.Form.ContainsKey("last_name")) { - if (Request.Form.ContainsKey("first_name") && - Request.Form.ContainsKey("last_name")) - { - return Ok(); - } - - return BadRequest(); + return Ok(); } - [HttpPost("upload-image")] - public IActionResult PostFile() + return BadRequest(); + } + + [HttpPost("upload-image")] + public IActionResult PostFile() + { + var files = Request.Form.Files; + if (files.Count == 1 && + files.First().FileName == "john_doe.jpg" && + files.First().ContentType == MediaTypeNames.Image.Jpeg) { - var files = Request.Form.Files; - if (files.Count == 1 && - files.First().FileName == "john_doe.jpg" && - files.First().ContentType == MediaTypeNames.Image.Jpeg) - { - return Ok(); - } - - return BadRequest(); + return Ok(); } + + return BadRequest(); } } \ No newline at end of file diff --git a/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/Program.cs b/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/Program.cs index bfd1fc26d7..580b1d782a 100644 --- a/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/Program.cs +++ b/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/Program.cs @@ -1,19 +1,17 @@ -public class Program -{ - private static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateBuilder(args); - // Add services to the container. +// Add services to the container. - builder.Services.AddControllers(); +builder.Services.AddControllers(); - var app = builder.Build(); +var app = builder.Build(); - // Configure the HTTP request pipeline. +// Configure the HTTP request pipeline. - app.MapControllers(); +app.MapControllers(); - app.Run(); - } -} \ No newline at end of file +app.Run(); + +// WebApplicationFactory in the test project needs Program to be a nameable, +// accessible type. Top-level statements generate an internal one, so we declare it here. +public partial class Program; \ No newline at end of file diff --git a/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/TestServer.csproj b/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/TestServer.csproj index 4c2bb77d01..a3a34b647c 100644 --- a/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/TestServer.csproj +++ b/aspnetcore-features/UsingMultipartFormDataInHttpClient/TestServer/TestServer.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable diff --git a/aspnetcore-features/UsingMultipartFormDataInHttpClient/Tests/MultipartFormDataTests.cs b/aspnetcore-features/UsingMultipartFormDataInHttpClient/Tests/MultipartFormDataTests.cs index a2de31c0c7..931afdb179 100644 --- a/aspnetcore-features/UsingMultipartFormDataInHttpClient/Tests/MultipartFormDataTests.cs +++ b/aspnetcore-features/UsingMultipartFormDataInHttpClient/Tests/MultipartFormDataTests.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc.Testing; using System.Net.Http.Headers; +using System.Net.Http.Json; using System.Net.Mime; using System.Text; @@ -23,10 +24,8 @@ public async Task WhenSendingMultiplePartsInPostRequest_ThenRequestIsSentSuccess multipartContent.Add(new StringContent("Doe", Encoding.UTF8, MediaTypeNames.Text.Plain), "last_name"); using var response = await _httpClient.PostAsync("http://localhost:5272/upload-form", multipartContent); - if (!response.IsSuccessStatusCode) - { - Assert.Fail("Request has returned an error status."); - } + + response.EnsureSuccessStatusCode(); } [Fact] @@ -34,16 +33,14 @@ public async Task WhenSendingFileInMultipartRequest_ThenRequestIsSentSuccessfull { using MultipartFormDataContent multipartContent = new(); - var imageContent = new StreamContent(File.OpenRead($".{Path.DirectorySeparatorChar}john_doe.jpg")); + var imageContent = new StreamContent(File.OpenRead("john_doe.jpg")); imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Image.Jpeg); multipartContent.Add(imageContent, "profile_picture", "john_doe.jpg"); using var response = await _httpClient.PostAsync("http://localhost:5272/upload-image", multipartContent); - if (!response.IsSuccessStatusCode) - { - Assert.Fail("Request has returned an error status."); - } + + response.EnsureSuccessStatusCode(); } [Fact] @@ -51,16 +48,32 @@ public async Task WhenSendingByteArrayInMultipartRequest_ThenRequestIsSentSucces { using MultipartFormDataContent multipartContent = new(); - var byteArrayContent = new ByteArrayContent(await File.ReadAllBytesAsync($".{Path.DirectorySeparatorChar}john_doe.jpg")); + var byteArrayContent = new ByteArrayContent(await File.ReadAllBytesAsync("john_doe.jpg")); byteArrayContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Image.Jpeg); multipartContent.Add(byteArrayContent, "profile_picture", "john_doe.jpg"); using var response = await _httpClient.PostAsync("http://localhost:5272/upload-image", multipartContent); - if (!response.IsSuccessStatusCode) - { - Assert.Fail("Request has returned an error status."); - } + + response.EnsureSuccessStatusCode(); + } + + [Fact] + public async Task WhenSendingJsonPartInMultipartRequest_ThenPartCarriesJsonContentType() + { + using MultipartFormDataContent multipartContent = new(); + + var jsonContent = JsonContent.Create(new { City = "Belgrade", Country = "Serbia" }); + + multipartContent.Add(new StringContent("John", Encoding.UTF8, MediaTypeNames.Text.Plain), "first_name"); + multipartContent.Add(new StringContent("Doe", Encoding.UTF8, MediaTypeNames.Text.Plain), "last_name"); + multipartContent.Add(jsonContent, "address"); + + Assert.Equal("application/json; charset=utf-8", jsonContent.Headers.ContentType?.ToString()); + + using var response = await _httpClient.PostAsync("http://localhost:5272/upload-form", multipartContent); + + response.EnsureSuccessStatusCode(); } } } \ No newline at end of file diff --git a/aspnetcore-features/UsingMultipartFormDataInHttpClient/Tests/Tests.csproj b/aspnetcore-features/UsingMultipartFormDataInHttpClient/Tests/Tests.csproj index 34732b9ef8..d3ec28913c 100644 --- a/aspnetcore-features/UsingMultipartFormDataInHttpClient/Tests/Tests.csproj +++ b/aspnetcore-features/UsingMultipartFormDataInHttpClient/Tests/Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable @@ -9,14 +9,14 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all