diff --git a/src/main/resources/csharp/api.mustache b/src/main/resources/csharp/api.mustache index 0b0f36e1c38..3d556fa72f8 100644 --- a/src/main/resources/csharp/api.mustache +++ b/src/main/resources/csharp/api.mustache @@ -45,6 +45,7 @@ // query params var queryParams = new Dictionary(); var headerParams = new Dictionary(); + var formParams = new Dictionary(); {{#requiredParamCount}} // verify required params are set @@ -53,9 +54,8 @@ } {{/requiredParamCount}} - string paramStr = null; {{#queryParams}}if ({{paramName}} != null){ - paramStr = ({{paramName}} != null && {{paramName}} is DateTime) ? ((DateTime)(object){{paramName}}).ToString("u") : Convert.ToString({{paramName}}); + string paramStr = ({{paramName}} is DateTime) ? ((DateTime)(object){{paramName}}).ToString("u") : Convert.ToString({{paramName}}); queryParams.Add("{{paramName}}", paramStr); } {{/queryParams}} @@ -63,17 +63,32 @@ {{#headerParams}}headerParams.Add("{{paramName}}", {{paramName}}); {{/headerParams}} - try { - var response = apiInvoker.invokeAPI(basePath, path, "{{httpMethod}}", queryParams, {{#bodyParam}}{{bodyParam}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, headerParams); - if(response != null){ - return {{#returnType}}({{{returnType}}}) ApiInvoker.deserialize(response, typeof({{{returnType}}})){{/returnType}}; + {{#formParams}}if ({{paramName}} != null){ + if({{paramName}} is byte[]) { + formParams.Add("{{paramName}}", {{paramName}}); + } else { + string paramStr = ({{paramName}} is DateTime) ? ((DateTime)(object){{paramName}}).ToString("u") : Convert.ToString({{paramName}}); + formParams.Add("{{paramName}}", paramStr); } - else { - return {{#returnType}}null{{/returnType}}; + } + {{/formParams}} + + try { + if (typeof({{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}) == typeof(byte[])) { + var response = apiInvoker.invokeBinaryAPI(basePath, path, "GET", queryParams, null, headerParams, formParams); + return {{#returnType}}((object)response) as {{{returnType}}}{{/returnType}}; + } else { + var response = apiInvoker.invokeAPI(basePath, path, "{{httpMethod}}", queryParams, {{#bodyParam}}{{bodyParam}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, headerParams, formParams); + if(response != null){ + return {{#returnType}}({{{returnType}}}) ApiInvoker.deserialize(response, typeof({{{returnType}}})){{/returnType}}; + } + else { + return {{#returnType}}null{{/returnType}}; + } } } catch (ApiException ex) { if(ex.ErrorCode == 404) { - return {{#returnType}} null{{/returnType}}; + return {{#returnType}}null{{/returnType}}; } else { throw ex; diff --git a/src/main/resources/csharp/apiInvoker.mustache b/src/main/resources/csharp/apiInvoker.mustache index 412f894792c..f7c76ee2c56 100644 --- a/src/main/resources/csharp/apiInvoker.mustache +++ b/src/main/resources/csharp/apiInvoker.mustache @@ -44,7 +44,17 @@ } } - public string invokeAPI(string host, string path, string method, Dictionary queryParams, object body, Dictionary headerParams) { + public string invokeAPI(string host, string path, string method, Dictionary queryParams, object body, Dictionary headerParams, Dictionary formParams) + { + return invokeAPIInternal(host, path, method, false, queryParams, body, headerParams, formParams) as string; + } + + public byte[] invokeBinaryAPI(string host, string path, string method, Dictionary queryParams, object body, Dictionary headerParams, Dictionary formParams) + { + return invokeAPIInternal(host, path, method, true, queryParams, body, headerParams, formParams) as byte[]; + } + + private object invokeAPIInternal(string host, string path, string method, bool binaryResponse, Dictionary queryParams, object body, Dictionary headerParams, Dictionary formParams) { var b = new StringBuilder(); foreach (var queryParamItem in queryParams) @@ -60,9 +70,21 @@ host = host.EndsWith("/") ? host.Substring(0, host.Length - 1) : host; var client = WebRequest.Create(host + path + querystring); - client.ContentType = "application/json"; client.Method = method; + byte[] formData = null; + if (formParams.Count > 0) + { + string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid()); + client.ContentType = "multipart/form-data; boundary=" + formDataBoundary; + formData = GetMultipartFormData(formParams, formDataBoundary); + client.ContentLength = formData.Length; + } + else + { + client.ContentType = "application/json"; + } + foreach (var headerParamsItem in headerParams) { client.Headers.Add(headerParamsItem.Key, headerParamsItem.Value); @@ -79,9 +101,17 @@ case "POST": case "PUT": case "DELETE": - var swRequestWriter = new StreamWriter(client.GetRequestStream()); - swRequestWriter.Write(serialize(body)); - swRequestWriter.Close(); + using (Stream requestStream = client.GetRequestStream()) + { + if (formData != null) + { + requestStream.Write(formData, 0, formData.Length); + } + + var swRequestWriter = new StreamWriter(requestStream); + swRequestWriter.Write(serialize(body)); + swRequestWriter.Close(); + } break; default: throw new ApiException(500, "unknown method type " + method); @@ -96,10 +126,22 @@ throw new ApiException((int)webResponse.StatusCode, webResponse.StatusDescription); } - var responseReader = new StreamReader(webResponse.GetResponseStream()); - var responseData = responseReader.ReadToEnd(); - responseReader.Close(); - return responseData; + if (binaryResponse) + { + using (var memoryStream = new MemoryStream()) + { + webResponse.GetResponseStream().CopyTo(memoryStream); + return memoryStream.ToArray(); + } + } + else + { + using (var responseReader = new StreamReader(webResponse.GetResponseStream())) + { + var responseData = responseReader.ReadToEnd(); + return responseData; + } + } } catch(WebException ex) { @@ -113,5 +155,53 @@ throw new ApiException(statusCode, ex.Message); } } + + private static byte[] GetMultipartFormData(Dictionary postParameters, string boundary) + { + Stream formDataStream = new System.IO.MemoryStream(); + bool needsCLRF = false; + + foreach (var param in postParameters) + { + // Thanks to feedback from commenters, add a CRLF to allow multiple parameters to be added. + // Skip it on the first parameter, add it to subsequent parameters. + if (needsCLRF) + formDataStream.Write(Encoding.UTF8.GetBytes("\r\n"), 0, Encoding.UTF8.GetByteCount("\r\n")); + + needsCLRF = true; + + if (param.Value is byte[]) + { + string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n", + boundary, + param.Key, + "application/octet-stream"); + formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData)); + + // Write the file data directly to the Stream, rather than serializing it to a string. + formDataStream.Write((param.Value as byte[]), 0, (param.Value as byte[]).Length); + } + else + { + string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}", + boundary, + param.Key, + param.Value); + formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData)); + } + } + + // Add the end of the request. Start with a newline + string footer = "\r\n--" + boundary + "--\r\n"; + formDataStream.Write(Encoding.UTF8.GetBytes(footer), 0, Encoding.UTF8.GetByteCount(footer)); + + // Dump the Stream into a byte[] + formDataStream.Position = 0; + byte[] formData = new byte[formDataStream.Length]; + formDataStream.Read(formData, 0, formData.Length); + formDataStream.Close(); + + return formData; + } } } diff --git a/src/main/scala/com/wordnik/swagger/codegen/BasicCSharpGenerator.scala b/src/main/scala/com/wordnik/swagger/codegen/BasicCSharpGenerator.scala index 873e66fd449..3490ff00a7f 100644 --- a/src/main/scala/com/wordnik/swagger/codegen/BasicCSharpGenerator.scala +++ b/src/main/scala/com/wordnik/swagger/codegen/BasicCSharpGenerator.scala @@ -41,7 +41,7 @@ class BasicCSharpGenerator extends BasicGenerator { * We are using csharp objects instead of primitives to avoid showing default * primitive values when the API returns missing data. For instance, having a * {"count":0} != count is unknown. You can change this to use primitives if you - * desire, but update the default values as well or they'll be set to null in + * desire, but update the default values as well or they'll be set to null in * variable declarations. */ override def typeMapping = Map( @@ -54,7 +54,9 @@ class BasicCSharpGenerator extends BasicGenerator { "double" -> "double?", "object" -> "object", "Date" -> "DateTime?", - "date" -> "DateTime?") + "date" -> "DateTime?", + "File" -> "byte[]", + "file" -> "byte[]") // location of templates override def templateDir = "csharp" @@ -70,11 +72,11 @@ class BasicCSharpGenerator extends BasicGenerator { // template used for models apiTemplateFiles += "api.mustache" -> ".cs" - override def reservedWords = Set("abstract", "continue", "for", "new", "switch", "assert", - "default", "if", "package", "synchronized", "do", "goto", "private", "this", "break", - "implements", "protected", "throw", "else", "import", "public", "throws", "case", - "enum", "instanceof", "return", "transient", "catch", "extends", "try", "final", - "interface", "static", "void", "class", "finally", "strictfp", "volatile", "const", + override def reservedWords = Set("abstract", "continue", "for", "new", "switch", "assert", + "default", "if", "package", "synchronized", "do", "goto", "private", "this", "break", + "implements", "protected", "throw", "else", "import", "public", "throws", "case", + "enum", "instanceof", "return", "transient", "catch", "extends", "try", "final", + "interface", "static", "void", "class", "finally", "strictfp", "volatile", "const", "native", "super", "while") // import/require statements for specific datatypes @@ -180,7 +182,7 @@ class BasicCSharpGenerator extends BasicGenerator { } override def escapeReservedWord(word: String) = { - if (reservedWords.contains(word)) + if (reservedWords.contains(word)) throw new Exception("reserved word " + "\"" + word + "\" not allowed") else word } @@ -193,8 +195,8 @@ class BasicCSharpGenerator extends BasicGenerator { capitalize(name) } - def capitalize(s: String) = { - s(0).toUpper + s.substring(1, s.length).toLowerCase + def capitalize(s: String) = { + s(0).toUpper + s.substring(1, s.length).toLowerCase }*/ // supporting classes