Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ private static List<KeyValuePair<string, object>> FlattenParamsValue(object valu
flatParams = SingleParam(keyPrefix, s);
break;

case MultipartFileContent f:
flatParams = SingleParam(keyPrefix, f);
break;

case Stream s:
flatParams = SingleParam(keyPrefix, s);
break;
Expand Down
4 changes: 4 additions & 0 deletions src/Stripe.net/Infrastructure/FormEncoding/FormEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ private static List<KeyValuePair<string, object>> FlattenParamsValue(object valu
flatParams = SingleParam(keyPrefix, s);
break;

case MultipartFileContent f:
flatParams = SingleParam(keyPrefix, f);
break;

case Stream s:
flatParams = SingleParam(keyPrefix, s);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,29 @@ public MultipartFormDataContent(
private static StringContent CreateStringContent(string value)
=> new StringContent(value, System.Text.Encoding.UTF8);

private static StreamContent CreateStreamContent(Stream value, string name)
private static StreamContent CreateStreamContent(MultipartFileContent value, string name)
{
var fileName = "blob";
var extension = string.Empty;
var fileName = value.Name ?? "blob";
var extension = Path.GetExtension(fileName);
var stream = value.Data;

FileStream fileStream = value as FileStream;
FileStream fileStream = stream as FileStream;
if ((fileStream != null) && (!string.IsNullOrEmpty(fileStream.Name)))
{
fileName = fileStream.Name;
extension = Path.GetExtension(fileName);
}

var content = new StreamContent(value);
var type = value.Type ?? MimeTypes.GetMimeType(extension);

var content = new StreamContent(stream);
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = name,
FileName = fileName,
FileNameStar = fileName,
};
content.Headers.ContentType = new MediaTypeHeaderValue(MimeTypes.GetMimeType(extension));
content.Headers.ContentType = new MediaTypeHeaderValue(type);
return content;
}

Expand All @@ -79,8 +82,16 @@ private void ProcessParameters(IEnumerable<KeyValuePair<string, object>> nameVal
this.Add(CreateStringContent(s), QuoteString(kvp.Key));
break;

case MultipartFileContent f:
this.Add(CreateStreamContent(f, QuoteString(kvp.Key)));
break;

case Stream s:
this.Add(CreateStreamContent(s, QuoteString(kvp.Key)));
var fileData = new MultipartFileContent
{
Data = s,
};
this.Add(CreateStreamContent(fileData, QuoteString(kvp.Key)));
break;

default:
Expand Down
3 changes: 1 addition & 2 deletions src/Stripe.net/Services/Files/FileCreateOptions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// File generated from our OpenAPI spec
namespace Stripe
{
using System.IO;
using Newtonsoft.Json;
#if NET6_0_OR_GREATER
using STJS = System.Text.Json.Serialization;
Expand All @@ -17,7 +16,7 @@ public class FileCreateOptions : BaseOptions
#if NET6_0_OR_GREATER
[STJS.JsonPropertyName("file")]
#endif
public Stream File { get; set; }
public MultipartFileContent File { get; set; }

/// <summary>
/// Optional parameters that automatically create a <a
Expand Down
31 changes: 31 additions & 0 deletions src/Stripe.net/Services/_common/MultipartFileContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Stripe
{
using System.IO;

/// <summary>
/// Represents Data and optional Name and Type that will be encoded as multipart form
/// data. Used in e.g. FileService.Create.
///
/// </summary>
public class MultipartFileContent
{
/// <summary>
/// The file data to send. If this is a FileStream, the SDK will infer
/// the name and type from the file name and extension. If this is not
/// a FileStream set Name and Type to configure the file upload.
/// </summary>
public Stream Data { get; set; }

/// <summary>
/// The optional name to send with this file data. Uses the file name if omitted
/// and Data is a FileStream.
/// </summary>
public string Name { get; set; }

/// <summary>
/// The optional mime type to use when sending file data. Uses the type that
/// matches the file extension from Name (or the file name from Data) if omitted.
/// </summary>
public string Type { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace StripeTests
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Stripe;
using Stripe.Infrastructure.FormEncoding;
using Xunit;

Expand Down Expand Up @@ -71,6 +72,132 @@ public async Task Ctor_OneStreamEntry_Success()
result);
}

[Fact]
public async Task Ctor_OneMultipartFileContentEntry_Success()
{
var source = new Dictionary<string, object>
{
{ "key", new MultipartFileContent { Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")) } },
};
var content = new MultipartFormDataContent(source, "test-boundary");

var stream = await content.ReadAsStreamAsync();
Assert.Equal(174, stream.Length);
var result = new StreamReader(stream).ReadToEnd();
Assert.Equal(
"--test-boundary\r\n"
+ "Content-Disposition: form-data; name=\"key\"; filename=blob; filename*=utf-8''blob\r\n"
+ "Content-Type: application/octet-stream\r\n\r\nHello World!\r\n"
+ "--test-boundary--\r\n",
result);
}

[Fact]
public async Task Ctor_OneMultipartFileContentWithNameEntry_Success()
{
var source = new Dictionary<string, object>
{
{
"key", new MultipartFileContent
{
Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")),
Name = "file",
}
},
};
var content = new MultipartFormDataContent(source, "test-boundary");

var stream = await content.ReadAsStreamAsync();
Assert.Equal(174, stream.Length);
var result = new StreamReader(stream).ReadToEnd();
Assert.Equal(
"--test-boundary\r\n"
+ "Content-Disposition: form-data; name=\"key\"; filename=file; filename*=utf-8''file\r\n"
+ "Content-Type: application/octet-stream\r\n\r\nHello World!\r\n"
+ "--test-boundary--\r\n",
result);
}

[Fact]
public async Task Ctor_OneMultipartFileContentWithNameAndExtEntry_Success()
{
var source = new Dictionary<string, object>
{
{
"key", new MultipartFileContent
{
Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")),
Name = "file.csv",
}
},
};
var content = new MultipartFormDataContent(source, "test-boundary");

var stream = await content.ReadAsStreamAsync();
Assert.Equal(166, stream.Length);
var result = new StreamReader(stream).ReadToEnd();
Assert.Equal(
"--test-boundary\r\n"
+ "Content-Disposition: form-data; name=\"key\"; filename=file.csv; filename*=utf-8''file.csv\r\n"
+ "Content-Type: text/csv\r\n\r\nHello World!\r\n"
+ "--test-boundary--\r\n",
result);
}

[Fact]
public async Task Ctor_OneMultipartFileContentWithNameAndTypeEntry_Success()
{
var source = new Dictionary<string, object>
{
{
"key", new MultipartFileContent
{
Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")),
Name = "file",
Type = "application/json",
}
},
};
var content = new MultipartFormDataContent(source, "test-boundary");

var stream = await content.ReadAsStreamAsync();
Assert.Equal(166, stream.Length);
var result = new StreamReader(stream).ReadToEnd();
Assert.Equal(
"--test-boundary\r\n"
+ "Content-Disposition: form-data; name=\"key\"; filename=file; filename*=utf-8''file\r\n"
+ "Content-Type: application/json\r\n\r\nHello World!\r\n"
+ "--test-boundary--\r\n",
result);
}

[Fact]
public async Task Ctor_OneMultipartFileContentWithNameAndExtAndTypeEntry_Success()
{
var source = new Dictionary<string, object>
{
{
"key", new MultipartFileContent
{
Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")),
Name = "file.json",
Type = "application/octet-stream",
}
},
};
var content = new MultipartFormDataContent(source, "test-boundary");

var stream = await content.ReadAsStreamAsync();
Assert.Equal(184, stream.Length);
var result = new StreamReader(stream).ReadToEnd();
Assert.Equal(
"--test-boundary\r\n"
+ "Content-Disposition: form-data; name=\"key\"; filename=file.json; filename*=utf-8''file.json\r\n"
+ "Content-Type: application/octet-stream\r\n\r\nHello World!\r\n"
+ "--test-boundary--\r\n",
result);
}

[Fact]
public async Task Ctor_TwoEntries_Success()
{
Expand Down
11 changes: 9 additions & 2 deletions src/StripeTests/Services/Files/FileServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ public FileServiceTest(
{
this.service = new FileService(this.StripeClient);

var resourceStream = typeof(FileServiceTest).GetTypeInfo().Assembly.GetManifestResourceStream(FileName);
this.createOptions = new FileCreateOptions
{
File = typeof(FileServiceTest).GetTypeInfo().Assembly.GetManifestResourceStream(FileName),
File = new MultipartFileContent
{
Data = resourceStream,
},
FileLinkData = new FileFileLinkDataOptions
{
Create = true,
Expand All @@ -44,7 +48,10 @@ public FileServiceTest(

this.base64Options = new FileCreateOptions
{
File = new MemoryStream(Convert.FromBase64String("c3RyaXBlLWRvdG5ldA==")),
File = new MultipartFileContent
{
Data = new MemoryStream(Convert.FromBase64String("c3RyaXBlLWRvdG5ldA==")),
},
Purpose = FilePurpose.BusinessLogo,
};

Expand Down
7 changes: 5 additions & 2 deletions src/StripeTests/Services/GeneratedExamplesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1330,8 +1330,11 @@ public void TestFilesPost()
var options = new FileCreateOptions
{
Purpose = "account_requirement",
File = new System.IO.MemoryStream(
System.Text.Encoding.UTF8.GetBytes("File contents")),
File = new Stripe.MultipartFileContent
{
Data = new System.IO.MemoryStream(
System.Text.Encoding.UTF8.GetBytes("File contents")),
},
};
var service = new FileService(this.StripeClient);
File file = service.Create(options);
Expand Down
Loading