diff --git a/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj b/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj index 8644bb21b..e1da87b9a 100644 --- a/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj +++ b/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/docs/error-handling.md b/docs/error-handling.md index 01b96b6da..60508ee3f 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -63,4 +63,4 @@ Below you can find how different extensions deal with errors. Note that function | `HeadAsync` | Yes | | `HeadAsync` | Yes | -In addition, all the functions for JSON requests, like `GetJsonAsync` and `PostJsonAsyn` throw an exception if the HTTP call fails. +In addition, all the functions for JSON requests, like `GetJsonAsync` and `PostJsonAsync` throw an exception if the HTTP call fails. diff --git a/docs/usage.md b/docs/usage.md index 8e41cc314..150df78f8 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -561,6 +561,22 @@ AddFile(parameterName, getFile, fileName, contentType); Remember that `AddFile` will set all the necessary headers, so please don't try to set content headers manually. +You can also provide file upload options to the `AddFile` call. The options are: +- `DisableFilenameEncoding` (default `false`): if set to `true`, RestSharp will not encode the file name in the `Content-Disposition` header +- `DisableFilenameStar` (default `true`): if set to `true`, RestSharp will not add the `filename*` parameter to the `Content-Disposition` header + +Example of using the options: + +```csharp +var options = new FileParameterOptions { + DisableFilenameEncoding = true, + DisableFilenameStar = false +}; +request.AddFile("file", filePath, options: options); +``` + +The options specified in the snippet above usually help when you upload files with non-ASCII characters in their names. + ### Downloading binary data There are two functions that allow you to download binary data from the remote API. diff --git a/props/Common.props b/props/Common.props index e876e299a..26bf7321a 100644 --- a/props/Common.props +++ b/props/Common.props @@ -7,4 +7,7 @@ enable enable + + + \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 13dd1b1c0..69705872a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -21,7 +21,7 @@ - + diff --git a/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs b/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs index 318139ef0..96c19536c 100644 --- a/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs +++ b/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs @@ -84,19 +84,7 @@ public class CsvHelperSerializer : IDeserializer, IRestSerializer, ISerializer { using var csvWriter = new CsvWriter(stringWriter, _configuration); if (obj is IEnumerable records) { - // ReSharper disable once PossibleMultipleEnumeration - var enumerator = records.GetEnumerator(); - - if (enumerator.MoveNext() && enumerator.Current != null) { - csvWriter.WriteHeader(enumerator.Current.GetType()); - csvWriter.NextRecord(); - // ReSharper disable once PossibleMultipleEnumeration - csvWriter.WriteRecords(records); - } - - if (enumerator is IDisposable disposable) { - disposable.Dispose(); - } + csvWriter.WriteRecords(records); } else { csvWriter.WriteHeader(obj.GetType()); diff --git a/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj b/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj index 2168d422a..5fff5b301 100644 --- a/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj +++ b/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj @@ -1,6 +1,6 @@  - + diff --git a/src/RestSharp/Parameters/FileParameter.cs b/src/RestSharp/Parameters/FileParameter.cs index 75b314aa0..5b58bf44d 100644 --- a/src/RestSharp/Parameters/FileParameter.cs +++ b/src/RestSharp/Parameters/FileParameter.cs @@ -113,6 +113,11 @@ public static FileParameter FromFile( [PublicAPI] public class FileParameterOptions { - public bool DisableFileNameStar { get; set; } = true; + [Obsolete("Use DisableFilenameStar instead")] + public bool DisableFileNameStar { + get => DisableFilenameStar; + set => DisableFilenameStar = value; + } + public bool DisableFilenameStar { get; set; } = true; public bool DisableFilenameEncoding { get; set; } } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 4597143db..ec109e2e1 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -9,14 +9,14 @@ - - + + - + - + diff --git a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj index 92691e7f4..cd511eb9b 100644 --- a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj +++ b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj @@ -16,10 +16,8 @@ - - - + diff --git a/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs b/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs index d7cc03811..56740fc7f 100644 --- a/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs +++ b/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs @@ -10,17 +10,27 @@ public class UploadController : ControllerBase { [HttpPost] [Route("upload")] [SuppressMessage("Performance", "CA1822:Mark members as static")] - public async Task Upload([FromForm] FormFile formFile) { + public async Task Upload([FromForm] FormFile formFile, [FromQuery] bool checkFile = true) { + var file = formFile.File; + + if (!checkFile) { + return Ok(new UploadResponse(file.FileName, file.Length, true)); + } + var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets"); - var file = formFile.File; await using var stream = file.OpenReadStream(); var received = await stream.ReadAsBytes(default); - var expected = await System.IO.File.ReadAllBytesAsync(Path.Combine(assetPath, file.FileName)); - var response = new UploadResponse(file.FileName, file.Length, received.SequenceEqual(expected)); - return response; + try { + var expected = await System.IO.File.ReadAllBytesAsync(Path.Combine(assetPath, file.FileName)); + var response = new UploadResponse(file.FileName, file.Length, received.SequenceEqual(expected)); + return Ok(response); + } + catch (Exception e) { + return BadRequest(new { Message = e.Message, Filename = file.FileName }); + } } } diff --git a/test/RestSharp.Tests.Integrated/UploadFileTests.cs b/test/RestSharp.Tests.Integrated/UploadFileTests.cs index 843e9048e..fd9f2c498 100644 --- a/test/RestSharp.Tests.Integrated/UploadFileTests.cs +++ b/test/RestSharp.Tests.Integrated/UploadFileTests.cs @@ -7,58 +7,63 @@ namespace RestSharp.Tests.Integrated; public class UploadFileTests { readonly ITestOutputHelper _output; readonly RestClient _client; - readonly string _path = AppDomain.CurrentDomain.BaseDirectory; + readonly string _basePath = AppDomain.CurrentDomain.BaseDirectory; + readonly string _path; + readonly UploadResponse _expected; + + const string Filename = "Koala.jpg"; public UploadFileTests(TestServerFixture fixture, ITestOutputHelper output) { _output = output; - _client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true }); + // _client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true }); + _client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = false }); + _path = Path.Combine(_basePath, "Assets", Filename); + _expected = new UploadResponse(Filename, new FileInfo(_path).Length, true); } [Fact] public async Task Should_upload_from_file() { - const string filename = "Koala.jpg"; - - var path = Path.Combine(_path, "Assets", filename); - - var request = new RestRequest("upload").AddFile("file", path); + var request = new RestRequest("upload").AddFile("file", _path); var response = await _client.ExecutePostAsync(request); response.StatusCode.Should().Be(HttpStatusCode.OK); - var expected = new UploadResponse(filename, new FileInfo(path).Length, true); - _output.WriteLine(response.Content); - response.Data.Should().BeEquivalentTo(expected); + response.Data.Should().BeEquivalentTo(_expected); } [Fact] public async Task Should_upload_from_bytes() { - const string filename = "Koala.jpg"; + var bytes = await File.ReadAllBytesAsync(_path); - var path = Path.Combine(_path, "Assets", filename); - var bytes = await File.ReadAllBytesAsync(path); - - var request = new RestRequest("upload").AddFile("file", bytes, filename); + var request = new RestRequest("upload").AddFile("file", bytes, Filename); var response = await _client.ExecutePostAsync(request); - var expected = new UploadResponse(filename, new FileInfo(path).Length, true); - _output.WriteLine(response.Content); - response.Data.Should().BeEquivalentTo(expected); + response.Data.Should().BeEquivalentTo(_expected); } [Fact] public async Task Should_upload_from_stream() { - const string filename = "Koala.jpg"; + var request = new RestRequest("upload").AddFile("file", () => File.OpenRead(_path), Filename); + var response = await _client.ExecutePostAsync(request); - var path = Path.Combine(_path, "Assets", filename); + _output.WriteLine(response.Content); + response.Data.Should().BeEquivalentTo(_expected); + } - var request = new RestRequest("upload").AddFile("file", () => File.OpenRead(path), filename); - var response = await _client.ExecutePostAsync(request); + [Fact] + public async Task Should_upload_from_stream_non_ascii() { + const string nonAsciiFilename = "Präsentation_Export.zip"; + + var options = new FileParameterOptions { DisableFilenameEncoding = true, DisableFilenameStar = false}; - var expected = new UploadResponse(filename, new FileInfo(path).Length, true); + var request = new RestRequest("upload") + .AddFile("file", () => File.OpenRead(_path), nonAsciiFilename, options: options) + .AddQueryParameter("checkFile", "false"); + var response = await _client.ExecutePostAsync(request); _output.WriteLine(response.Content); - response.Data.Should().BeEquivalentTo(expected); + response.Data.Should().BeEquivalentTo(new UploadResponse(nonAsciiFilename, new FileInfo(_path).Length, true)); } } diff --git a/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs b/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs index 5901d14d1..e4aee68e8 100644 --- a/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs +++ b/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs @@ -137,11 +137,12 @@ public void SerializedObject_Should_Be() { DateTimeValue = new DateTime(2024, 1, 20) }; - serializer.Serialize(item) - .Should() - .Be( - "StringValue,Int32Value,DecimalValue,DoubleValue,SingleValue,DateTimeValue,TimeSpanValue;hello,32,0,0,16.5,01/20/2024 00:00:00,00:10:00;" - ); + var actual = serializer.Serialize(item); + + const string expected = + "StringValue,Int32Value,DecimalValue,DoubleValue,SingleValue,DateTimeValue,TimeSpanValue;hello,32,0,0,16.5,01/20/2024 00:00:00,00:10:00;"; + + actual.Should().Be(expected); } [Fact] diff --git a/test/RestSharp.Tests/RestSharp.Tests.csproj b/test/RestSharp.Tests/RestSharp.Tests.csproj index cdae3f398..eda460e5b 100644 --- a/test/RestSharp.Tests/RestSharp.Tests.csproj +++ b/test/RestSharp.Tests/RestSharp.Tests.csproj @@ -1,6 +1,6 @@  - +