Skip to content

Commit d9de57d

Browse files
committed
Merge pull request #223 from darkcube/master
CSharp: Multipart form submission
2 parents 4870ed7 + d0b0323 commit d9de57d

File tree

3 files changed

+135
-28
lines changed

3 files changed

+135
-28
lines changed

src/main/resources/csharp/api.mustache

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
// query params
4646
var queryParams = new Dictionary<String, String>();
4747
var headerParams = new Dictionary<String, String>();
48+
var formParams = new Dictionary<String, object>();
4849

4950
{{#requiredParamCount}}
5051
// verify required params are set
@@ -53,27 +54,41 @@
5354
}
5455
{{/requiredParamCount}}
5556

56-
string paramStr = null;
5757
{{#queryParams}}if ({{paramName}} != null){
58-
paramStr = ({{paramName}} != null && {{paramName}} is DateTime) ? ((DateTime)(object){{paramName}}).ToString("u") : Convert.ToString({{paramName}});
58+
string paramStr = ({{paramName}} is DateTime) ? ((DateTime)(object){{paramName}}).ToString("u") : Convert.ToString({{paramName}});
5959
queryParams.Add("{{paramName}}", paramStr);
6060
}
6161
{{/queryParams}}
6262

6363
{{#headerParams}}headerParams.Add("{{paramName}}", {{paramName}});
6464
{{/headerParams}}
6565

66-
try {
67-
var response = apiInvoker.invokeAPI(basePath, path, "{{httpMethod}}", queryParams, {{#bodyParam}}{{bodyParam}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, headerParams);
68-
if(response != null){
69-
return {{#returnType}}({{{returnType}}}) ApiInvoker.deserialize(response, typeof({{{returnType}}})){{/returnType}};
66+
{{#formParams}}if ({{paramName}} != null){
67+
if({{paramName}} is byte[]) {
68+
formParams.Add("{{paramName}}", {{paramName}});
69+
} else {
70+
string paramStr = ({{paramName}} is DateTime) ? ((DateTime)(object){{paramName}}).ToString("u") : Convert.ToString({{paramName}});
71+
formParams.Add("{{paramName}}", paramStr);
7072
}
71-
else {
72-
return {{#returnType}}null{{/returnType}};
73+
}
74+
{{/formParams}}
75+
76+
try {
77+
if (typeof({{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}) == typeof(byte[])) {
78+
var response = apiInvoker.invokeBinaryAPI(basePath, path, "GET", queryParams, null, headerParams, formParams);
79+
return {{#returnType}}((object)response) as {{{returnType}}}{{/returnType}};
80+
} else {
81+
var response = apiInvoker.invokeAPI(basePath, path, "{{httpMethod}}", queryParams, {{#bodyParam}}{{bodyParam}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, headerParams, formParams);
82+
if(response != null){
83+
return {{#returnType}}({{{returnType}}}) ApiInvoker.deserialize(response, typeof({{{returnType}}})){{/returnType}};
84+
}
85+
else {
86+
return {{#returnType}}null{{/returnType}};
87+
}
7388
}
7489
} catch (ApiException ex) {
7590
if(ex.ErrorCode == 404) {
76-
return {{#returnType}} null{{/returnType}};
91+
return {{#returnType}}null{{/returnType}};
7792
}
7893
else {
7994
throw ex;

src/main/resources/csharp/apiInvoker.mustache

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,17 @@
4444
}
4545
}
4646

47-
public string invokeAPI(string host, string path, string method, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams) {
47+
public string invokeAPI(string host, string path, string method, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams, Dictionary<String, object> formParams)
48+
{
49+
return invokeAPIInternal(host, path, method, false, queryParams, body, headerParams, formParams) as string;
50+
}
51+
52+
public byte[] invokeBinaryAPI(string host, string path, string method, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams, Dictionary<String, object> formParams)
53+
{
54+
return invokeAPIInternal(host, path, method, true, queryParams, body, headerParams, formParams) as byte[];
55+
}
56+
57+
private object invokeAPIInternal(string host, string path, string method, bool binaryResponse, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams, Dictionary<String, object> formParams) {
4858
var b = new StringBuilder();
4959
5060
foreach (var queryParamItem in queryParams)
@@ -60,9 +70,21 @@
6070
host = host.EndsWith("/") ? host.Substring(0, host.Length - 1) : host;
6171

6272
var client = WebRequest.Create(host + path + querystring);
63-
client.ContentType = "application/json";
6473
client.Method = method;
6574

75+
byte[] formData = null;
76+
if (formParams.Count > 0)
77+
{
78+
string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());
79+
client.ContentType = "multipart/form-data; boundary=" + formDataBoundary;
80+
formData = GetMultipartFormData(formParams, formDataBoundary);
81+
client.ContentLength = formData.Length;
82+
}
83+
else
84+
{
85+
client.ContentType = "application/json";
86+
}
87+
6688
foreach (var headerParamsItem in headerParams)
6789
{
6890
client.Headers.Add(headerParamsItem.Key, headerParamsItem.Value);
@@ -79,9 +101,17 @@
79101
case "POST":
80102
case "PUT":
81103
case "DELETE":
82-
var swRequestWriter = new StreamWriter(client.GetRequestStream());
83-
swRequestWriter.Write(serialize(body));
84-
swRequestWriter.Close();
104+
using (Stream requestStream = client.GetRequestStream())
105+
{
106+
if (formData != null)
107+
{
108+
requestStream.Write(formData, 0, formData.Length);
109+
}
110+
111+
var swRequestWriter = new StreamWriter(requestStream);
112+
swRequestWriter.Write(serialize(body));
113+
swRequestWriter.Close();
114+
}
85115
break;
86116
default:
87117
throw new ApiException(500, "unknown method type " + method);
@@ -96,10 +126,22 @@
96126
throw new ApiException((int)webResponse.StatusCode, webResponse.StatusDescription);
97127
}
98128

99-
var responseReader = new StreamReader(webResponse.GetResponseStream());
100-
var responseData = responseReader.ReadToEnd();
101-
responseReader.Close();
102-
return responseData;
129+
if (binaryResponse)
130+
{
131+
using (var memoryStream = new MemoryStream())
132+
{
133+
webResponse.GetResponseStream().CopyTo(memoryStream);
134+
return memoryStream.ToArray();
135+
}
136+
}
137+
else
138+
{
139+
using (var responseReader = new StreamReader(webResponse.GetResponseStream()))
140+
{
141+
var responseData = responseReader.ReadToEnd();
142+
return responseData;
143+
}
144+
}
103145
}
104146
catch(WebException ex)
105147
{
@@ -113,5 +155,53 @@
113155
throw new ApiException(statusCode, ex.Message);
114156
}
115157
}
158+
159+
private static byte[] GetMultipartFormData(Dictionary<string, object> postParameters, string boundary)
160+
{
161+
Stream formDataStream = new System.IO.MemoryStream();
162+
bool needsCLRF = false;
163+
164+
foreach (var param in postParameters)
165+
{
166+
// Thanks to feedback from commenters, add a CRLF to allow multiple parameters to be added.
167+
// Skip it on the first parameter, add it to subsequent parameters.
168+
if (needsCLRF)
169+
formDataStream.Write(Encoding.UTF8.GetBytes("\r\n"), 0, Encoding.UTF8.GetByteCount("\r\n"));
170+
171+
needsCLRF = true;
172+
173+
if (param.Value is byte[])
174+
{
175+
string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n",
176+
boundary,
177+
param.Key,
178+
"application/octet-stream");
179+
formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData));
180+
181+
// Write the file data directly to the Stream, rather than serializing it to a string.
182+
formDataStream.Write((param.Value as byte[]), 0, (param.Value as byte[]).Length);
183+
}
184+
else
185+
{
186+
string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}",
187+
boundary,
188+
param.Key,
189+
param.Value);
190+
formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData));
191+
}
192+
}
193+
194+
// Add the end of the request. Start with a newline
195+
string footer = "\r\n--" + boundary + "--\r\n";
196+
formDataStream.Write(Encoding.UTF8.GetBytes(footer), 0, Encoding.UTF8.GetByteCount(footer));
197+
198+
// Dump the Stream into a byte[]
199+
formDataStream.Position = 0;
200+
byte[] formData = new byte[formDataStream.Length];
201+
formDataStream.Read(formData, 0, formData.Length);
202+
formDataStream.Close();
203+
204+
return formData;
205+
}
116206
}
117207
}

src/main/scala/com/wordnik/swagger/codegen/BasicCSharpGenerator.scala

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class BasicCSharpGenerator extends BasicGenerator {
4141
* We are using csharp objects instead of primitives to avoid showing default
4242
* primitive values when the API returns missing data. For instance, having a
4343
* {"count":0} != count is unknown. You can change this to use primitives if you
44-
* desire, but update the default values as well or they'll be set to null in
44+
* desire, but update the default values as well or they'll be set to null in
4545
* variable declarations.
4646
*/
4747
override def typeMapping = Map(
@@ -54,7 +54,9 @@ class BasicCSharpGenerator extends BasicGenerator {
5454
"double" -> "double?",
5555
"object" -> "object",
5656
"Date" -> "DateTime?",
57-
"date" -> "DateTime?")
57+
"date" -> "DateTime?",
58+
"File" -> "byte[]",
59+
"file" -> "byte[]")
5860

5961
// location of templates
6062
override def templateDir = "csharp"
@@ -70,11 +72,11 @@ class BasicCSharpGenerator extends BasicGenerator {
7072
// template used for models
7173
apiTemplateFiles += "api.mustache" -> ".cs"
7274

73-
override def reservedWords = Set("abstract", "continue", "for", "new", "switch", "assert",
74-
"default", "if", "package", "synchronized", "do", "goto", "private", "this", "break",
75-
"implements", "protected", "throw", "else", "import", "public", "throws", "case",
76-
"enum", "instanceof", "return", "transient", "catch", "extends", "try", "final",
77-
"interface", "static", "void", "class", "finally", "strictfp", "volatile", "const",
75+
override def reservedWords = Set("abstract", "continue", "for", "new", "switch", "assert",
76+
"default", "if", "package", "synchronized", "do", "goto", "private", "this", "break",
77+
"implements", "protected", "throw", "else", "import", "public", "throws", "case",
78+
"enum", "instanceof", "return", "transient", "catch", "extends", "try", "final",
79+
"interface", "static", "void", "class", "finally", "strictfp", "volatile", "const",
7880
"native", "super", "while")
7981

8082
// import/require statements for specific datatypes
@@ -180,7 +182,7 @@ class BasicCSharpGenerator extends BasicGenerator {
180182
}
181183

182184
override def escapeReservedWord(word: String) = {
183-
if (reservedWords.contains(word))
185+
if (reservedWords.contains(word))
184186
throw new Exception("reserved word " + "\"" + word + "\" not allowed")
185187
else word
186188
}
@@ -193,8 +195,8 @@ class BasicCSharpGenerator extends BasicGenerator {
193195
capitalize(name)
194196
}
195197
196-
def capitalize(s: String) = {
197-
s(0).toUpper + s.substring(1, s.length).toLowerCase
198+
def capitalize(s: String) = {
199+
s(0).toUpper + s.substring(1, s.length).toLowerCase
198200
}*/
199201

200202
// supporting classes

0 commit comments

Comments
 (0)