Skip to content

Commit ccf0c39

Browse files
authored
Adds ability to specify file name and type when calling FileService.Create (#3171)
1 parent 6fb0d45 commit ccf0c39

File tree

8 files changed

+199
-13
lines changed

8 files changed

+199
-13
lines changed

src/Stripe.net/Infrastructure/FormEncoding/ContentEncoder.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ private static List<KeyValuePair<string, object>> FlattenParamsValue(object valu
145145
flatParams = SingleParam(keyPrefix, s);
146146
break;
147147

148+
case MultipartFileContent f:
149+
flatParams = SingleParam(keyPrefix, f);
150+
break;
151+
148152
case Stream s:
149153
flatParams = SingleParam(keyPrefix, s);
150154
break;

src/Stripe.net/Infrastructure/FormEncoding/FormEncoder.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ private static List<KeyValuePair<string, object>> FlattenParamsValue(object valu
116116
flatParams = SingleParam(keyPrefix, s);
117117
break;
118118

119+
case MultipartFileContent f:
120+
flatParams = SingleParam(keyPrefix, f);
121+
break;
122+
119123
case Stream s:
120124
flatParams = SingleParam(keyPrefix, s);
121125
break;

src/Stripe.net/Infrastructure/FormEncoding/MultipartFormDataContent.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,29 @@ public MultipartFormDataContent(
4141
private static StringContent CreateStringContent(string value)
4242
=> new StringContent(value, System.Text.Encoding.UTF8);
4343

44-
private static StreamContent CreateStreamContent(Stream value, string name)
44+
private static StreamContent CreateStreamContent(MultipartFileContent value, string name)
4545
{
46-
var fileName = "blob";
47-
var extension = string.Empty;
46+
var fileName = value.Name ?? "blob";
47+
var extension = Path.GetExtension(fileName);
48+
var stream = value.Data;
4849

49-
FileStream fileStream = value as FileStream;
50+
FileStream fileStream = stream as FileStream;
5051
if ((fileStream != null) && (!string.IsNullOrEmpty(fileStream.Name)))
5152
{
5253
fileName = fileStream.Name;
5354
extension = Path.GetExtension(fileName);
5455
}
5556

56-
var content = new StreamContent(value);
57+
var type = value.Type ?? MimeTypes.GetMimeType(extension);
58+
59+
var content = new StreamContent(stream);
5760
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
5861
{
5962
Name = name,
6063
FileName = fileName,
6164
FileNameStar = fileName,
6265
};
63-
content.Headers.ContentType = new MediaTypeHeaderValue(MimeTypes.GetMimeType(extension));
66+
content.Headers.ContentType = new MediaTypeHeaderValue(type);
6467
return content;
6568
}
6669

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

85+
case MultipartFileContent f:
86+
this.Add(CreateStreamContent(f, QuoteString(kvp.Key)));
87+
break;
88+
8289
case Stream s:
83-
this.Add(CreateStreamContent(s, QuoteString(kvp.Key)));
90+
var fileData = new MultipartFileContent
91+
{
92+
Data = s,
93+
};
94+
this.Add(CreateStreamContent(fileData, QuoteString(kvp.Key)));
8495
break;
8596

8697
default:

src/Stripe.net/Services/Files/FileCreateOptions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// File generated from our OpenAPI spec
22
namespace Stripe
33
{
4-
using System.IO;
54
using Newtonsoft.Json;
65
#if NET6_0_OR_GREATER
76
using STJS = System.Text.Json.Serialization;
@@ -17,7 +16,7 @@ public class FileCreateOptions : BaseOptions
1716
#if NET6_0_OR_GREATER
1817
[STJS.JsonPropertyName("file")]
1918
#endif
20-
public Stream File { get; set; }
19+
public MultipartFileContent File { get; set; }
2120

2221
/// <summary>
2322
/// Optional parameters that automatically create a <a
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace Stripe
2+
{
3+
using System.IO;
4+
5+
/// <summary>
6+
/// Represents Data and optional Name and Type that will be encoded as multipart form
7+
/// data. Used in e.g. FileService.Create.
8+
///
9+
/// </summary>
10+
public class MultipartFileContent
11+
{
12+
/// <summary>
13+
/// The file data to send. If this is a FileStream, the SDK will infer
14+
/// the name and type from the file name and extension. If this is not
15+
/// a FileStream set Name and Type to configure the file upload.
16+
/// </summary>
17+
public Stream Data { get; set; }
18+
19+
/// <summary>
20+
/// The optional name to send with this file data. Uses the file name if omitted
21+
/// and Data is a FileStream.
22+
/// </summary>
23+
public string Name { get; set; }
24+
25+
/// <summary>
26+
/// The optional mime type to use when sending file data. Uses the type that
27+
/// matches the file extension from Name (or the file name from Data) if omitted.
28+
/// </summary>
29+
public string Type { get; set; }
30+
}
31+
}

src/StripeTests/Infrastructure/FormEncoding/MultipartFormDataContentTest.cs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace StripeTests
55
using System.IO;
66
using System.Text;
77
using System.Threading.Tasks;
8+
using Stripe;
89
using Stripe.Infrastructure.FormEncoding;
910
using Xunit;
1011

@@ -71,6 +72,132 @@ public async Task Ctor_OneStreamEntry_Success()
7172
result);
7273
}
7374

75+
[Fact]
76+
public async Task Ctor_OneMultipartFileContentEntry_Success()
77+
{
78+
var source = new Dictionary<string, object>
79+
{
80+
{ "key", new MultipartFileContent { Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")) } },
81+
};
82+
var content = new MultipartFormDataContent(source, "test-boundary");
83+
84+
var stream = await content.ReadAsStreamAsync();
85+
Assert.Equal(174, stream.Length);
86+
var result = new StreamReader(stream).ReadToEnd();
87+
Assert.Equal(
88+
"--test-boundary\r\n"
89+
+ "Content-Disposition: form-data; name=\"key\"; filename=blob; filename*=utf-8''blob\r\n"
90+
+ "Content-Type: application/octet-stream\r\n\r\nHello World!\r\n"
91+
+ "--test-boundary--\r\n",
92+
result);
93+
}
94+
95+
[Fact]
96+
public async Task Ctor_OneMultipartFileContentWithNameEntry_Success()
97+
{
98+
var source = new Dictionary<string, object>
99+
{
100+
{
101+
"key", new MultipartFileContent
102+
{
103+
Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")),
104+
Name = "file",
105+
}
106+
},
107+
};
108+
var content = new MultipartFormDataContent(source, "test-boundary");
109+
110+
var stream = await content.ReadAsStreamAsync();
111+
Assert.Equal(174, stream.Length);
112+
var result = new StreamReader(stream).ReadToEnd();
113+
Assert.Equal(
114+
"--test-boundary\r\n"
115+
+ "Content-Disposition: form-data; name=\"key\"; filename=file; filename*=utf-8''file\r\n"
116+
+ "Content-Type: application/octet-stream\r\n\r\nHello World!\r\n"
117+
+ "--test-boundary--\r\n",
118+
result);
119+
}
120+
121+
[Fact]
122+
public async Task Ctor_OneMultipartFileContentWithNameAndExtEntry_Success()
123+
{
124+
var source = new Dictionary<string, object>
125+
{
126+
{
127+
"key", new MultipartFileContent
128+
{
129+
Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")),
130+
Name = "file.csv",
131+
}
132+
},
133+
};
134+
var content = new MultipartFormDataContent(source, "test-boundary");
135+
136+
var stream = await content.ReadAsStreamAsync();
137+
Assert.Equal(166, stream.Length);
138+
var result = new StreamReader(stream).ReadToEnd();
139+
Assert.Equal(
140+
"--test-boundary\r\n"
141+
+ "Content-Disposition: form-data; name=\"key\"; filename=file.csv; filename*=utf-8''file.csv\r\n"
142+
+ "Content-Type: text/csv\r\n\r\nHello World!\r\n"
143+
+ "--test-boundary--\r\n",
144+
result);
145+
}
146+
147+
[Fact]
148+
public async Task Ctor_OneMultipartFileContentWithNameAndTypeEntry_Success()
149+
{
150+
var source = new Dictionary<string, object>
151+
{
152+
{
153+
"key", new MultipartFileContent
154+
{
155+
Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")),
156+
Name = "file",
157+
Type = "application/json",
158+
}
159+
},
160+
};
161+
var content = new MultipartFormDataContent(source, "test-boundary");
162+
163+
var stream = await content.ReadAsStreamAsync();
164+
Assert.Equal(166, stream.Length);
165+
var result = new StreamReader(stream).ReadToEnd();
166+
Assert.Equal(
167+
"--test-boundary\r\n"
168+
+ "Content-Disposition: form-data; name=\"key\"; filename=file; filename*=utf-8''file\r\n"
169+
+ "Content-Type: application/json\r\n\r\nHello World!\r\n"
170+
+ "--test-boundary--\r\n",
171+
result);
172+
}
173+
174+
[Fact]
175+
public async Task Ctor_OneMultipartFileContentWithNameAndExtAndTypeEntry_Success()
176+
{
177+
var source = new Dictionary<string, object>
178+
{
179+
{
180+
"key", new MultipartFileContent
181+
{
182+
Data = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")),
183+
Name = "file.json",
184+
Type = "application/octet-stream",
185+
}
186+
},
187+
};
188+
var content = new MultipartFormDataContent(source, "test-boundary");
189+
190+
var stream = await content.ReadAsStreamAsync();
191+
Assert.Equal(184, stream.Length);
192+
var result = new StreamReader(stream).ReadToEnd();
193+
Assert.Equal(
194+
"--test-boundary\r\n"
195+
+ "Content-Disposition: form-data; name=\"key\"; filename=file.json; filename*=utf-8''file.json\r\n"
196+
+ "Content-Type: application/octet-stream\r\n\r\nHello World!\r\n"
197+
+ "--test-boundary--\r\n",
198+
result);
199+
}
200+
74201
[Fact]
75202
public async Task Ctor_TwoEntries_Success()
76203
{

src/StripeTests/Services/Files/FileServiceTest.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ public FileServiceTest(
2828
{
2929
this.service = new FileService(this.StripeClient);
3030

31+
var resourceStream = typeof(FileServiceTest).GetTypeInfo().Assembly.GetManifestResourceStream(FileName);
3132
this.createOptions = new FileCreateOptions
3233
{
33-
File = typeof(FileServiceTest).GetTypeInfo().Assembly.GetManifestResourceStream(FileName),
34+
File = new MultipartFileContent
35+
{
36+
Data = resourceStream,
37+
},
3438
FileLinkData = new FileFileLinkDataOptions
3539
{
3640
Create = true,
@@ -44,7 +48,10 @@ public FileServiceTest(
4448

4549
this.base64Options = new FileCreateOptions
4650
{
47-
File = new MemoryStream(Convert.FromBase64String("c3RyaXBlLWRvdG5ldA==")),
51+
File = new MultipartFileContent
52+
{
53+
Data = new MemoryStream(Convert.FromBase64String("c3RyaXBlLWRvdG5ldA==")),
54+
},
4855
Purpose = FilePurpose.BusinessLogo,
4956
};
5057

src/StripeTests/Services/GeneratedExamplesTest.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,8 +1330,11 @@ public void TestFilesPost()
13301330
var options = new FileCreateOptions
13311331
{
13321332
Purpose = "account_requirement",
1333-
File = new System.IO.MemoryStream(
1334-
System.Text.Encoding.UTF8.GetBytes("File contents")),
1333+
File = new Stripe.MultipartFileContent
1334+
{
1335+
Data = new System.IO.MemoryStream(
1336+
System.Text.Encoding.UTF8.GetBytes("File contents")),
1337+
},
13351338
};
13361339
var service = new FileService(this.StripeClient);
13371340
File file = service.Create(options);

0 commit comments

Comments
 (0)