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
33 changes: 24 additions & 9 deletions src/main/resources/csharp/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
// query params
var queryParams = new Dictionary<String, String>();
var headerParams = new Dictionary<String, String>();
var formParams = new Dictionary<String, object>();

{{#requiredParamCount}}
// verify required params are set
Expand All @@ -53,27 +54,41 @@
}
{{/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}}

{{#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;
Expand Down
108 changes: 99 additions & 9 deletions src/main/resources/csharp/apiInvoker.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,17 @@
}
}

public string invokeAPI(string host, string path, string method, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams) {
public string invokeAPI(string host, string path, string method, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams, Dictionary<String, object> formParams)
{
return invokeAPIInternal(host, path, method, false, queryParams, body, headerParams, formParams) as string;
}

public byte[] invokeBinaryAPI(string host, string path, string method, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams, Dictionary<String, object> 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<String, String> queryParams, object body, Dictionary<String, String> headerParams, Dictionary<String, object> formParams) {
var b = new StringBuilder();

foreach (var queryParamItem in queryParams)
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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)
{
Expand All @@ -113,5 +155,53 @@
throw new ApiException(statusCode, ex.Message);
}
}

private static byte[] GetMultipartFormData(Dictionary<string, object> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down