diff --git a/Templates/CSharp/Base/SharedCSharp.template.tt b/Templates/CSharp/Base/SharedCSharp.template.tt index 32bd671e3..95031f75c 100644 --- a/Templates/CSharp/Base/SharedCSharp.template.tt +++ b/Templates/CSharp/Base/SharedCSharp.template.tt @@ -314,4 +314,34 @@ public string GetRequestMethodWithOptionsHeader() /// The built request."; } +// ------------------------------------------------------------- +// Methods used in MethodRequest.cs.tt and IMethodRequest.cs.tt for OData actions and functions. +// ------------------------------------------------------------- + +/// +/// Used in MethodRequest.cs.tt and IMethodRequest.cs.tt to get the ODataMethod*Response type +/// defined in Microsoft.Graph.Core. Updates to supported OData primitives for OData methods +/// needs to occur in MethodRequest.cs.tt, IMethodRequest.cs.tt, Microsoft.Graph.Core, and here. +/// +/// +/// +public string GetMethodRequestPrimitiveReturnTypeString(string type) +{ + switch (type.ToLowerInvariant()) + { + case "string": + return "ODataMethodStringResponse"; + case "int32": + return "ODataMethodIntResponse"; + case "boolean": + case "bool": + return "ODataMethodBooleanResponse"; + case "int64": + return "ODataMethodLongResponse"; + default: + return type; + } +} + + #> diff --git a/Templates/CSharp/Requests/IMethodRequest.cs.tt b/Templates/CSharp/Requests/IMethodRequest.cs.tt index 8756ac348..abd559cc8 100644 --- a/Templates/CSharp/Requests/IMethodRequest.cs.tt +++ b/Templates/CSharp/Requests/IMethodRequest.cs.tt @@ -18,19 +18,42 @@ var requestType = entityName + methodName + "Request"; var returnEntityType = method.ReturnType == null ? null : method.ReturnType.GetTypeString(@namespace); var returnEntityParameter = string.Empty; -if (returnEntityType != null) {returnEntityParameter = returnEntityType.ToLower();} +if (returnEntityType != null) +{ + returnEntityParameter = returnEntityType.ToLower(); + + // Updates to supported OData primitives need to occur here, + // IMethodRequest.cs.tt, Microsoft.Graph.Core, and in + // GetMethodRequestPrimitiveReturnTypeString() in SharedCSharp. + var tempReturnType = GetMethodRequestPrimitiveReturnTypeString(returnEntityType); + + // These magic strings represent types in Microsoft.Graph.Core. + // If the return type is a primitive, then make it nullable. + if (tempReturnType == "ODataMethodIntResponse" || + tempReturnType == "ODataMethodBooleanResponse" || + tempReturnType == "ODataMethodLongResponse") + { + returnEntityType = returnEntityType + "?"; + } +} var returnTypeObject = method.ReturnType == null ? null : method.ReturnType.AsOdcmClass(); + + var isCollection = method.IsCollection; var sendAsyncReturnType = isCollection ? "I" + entityName + methodName + "CollectionPage" : returnEntityType; + + var methodReturnType = sendAsyncReturnType == null ? "System.Threading.Tasks.Task" : "System.Threading.Tasks.Task<" + sendAsyncReturnType + ">"; + + bool hasParameters = method.Parameters != null && method.Parameters.Any(); bool includeRequestBody = hasParameters && isAction; bool returnsStream = string.Equals(sendAsyncReturnType, "Stream"); diff --git a/Templates/CSharp/Requests/MethodRequest.cs.tt b/Templates/CSharp/Requests/MethodRequest.cs.tt index e1ecce8a4..31b4db233 100644 --- a/Templates/CSharp/Requests/MethodRequest.cs.tt +++ b/Templates/CSharp/Requests/MethodRequest.cs.tt @@ -16,28 +16,79 @@ var entityName = method.Class.Name.ToCheckedCase(); var isFunction = method.IsFunction; var isAction = !isFunction; var isComposable = method.IsComposable; +var isCollection = method.IsCollection; var methodName = method.Name.Substring(method.Name.IndexOf('.') + 1).ToCheckedCase(); var requestType = entityName + methodName + "Request"; -var returnEntityType = method.ReturnType == null ? null : method.ReturnType.GetTypeString(@namespace); - var returnEntityParameter = string.Empty; -if (returnEntityType != null) {returnEntityParameter = returnEntityType.ToLower();} -var isCollection = method.IsCollection; +// Represents the return of the SendAsync call within a public GetSync() or PostAsync() call. +var sendAsyncReturnType = string.Empty; -var sendAsyncReturnType = isCollection - ? "I" + entityName + methodName + "CollectionPage" - : returnEntityType; +// Indicates whether the OData method returns an OData primitive (non-collection). +// Collections of OData primitives is already supported. +var isPrimitiveReturnType = false; -var methodReturnType = sendAsyncReturnType == null - ? "System.Threading.Tasks.Task" - : "System.Threading.Tasks.Task<" + sendAsyncReturnType + ">"; +// Represents the return type of a GetAsync() or PostAsync() call. +var returnEntityType = method.ReturnType == null ? null : method.ReturnType.GetTypeString(@namespace); + +// Set the SendAsync return type and determine whether we are working with an OData primitive. +if (returnEntityType != null) +{ + returnEntityParameter = returnEntityType.ToLower(); + if (isCollection) + { + sendAsyncReturnType = "I" + entityName + methodName + "CollectionPage"; + } + else + { + // Updates to supported OData primitives need to occur here, + // IMethodRequest.cs.tt, Microsoft.Graph.Core, and in + // GetMethodRequestPrimitiveReturnTypeString() in SharedCSharp. + sendAsyncReturnType = GetMethodRequestPrimitiveReturnTypeString(returnEntityType); + + // These magic strings represent types in M.G.C. + if (sendAsyncReturnType == "ODataMethodStringResponse" || + sendAsyncReturnType == "ODataMethodIntResponse" || + sendAsyncReturnType == "ODataMethodBooleanResponse" || + sendAsyncReturnType == "ODataMethodLongResponse") + { + isPrimitiveReturnType = true; + } + } +} +else +{ + sendAsyncReturnType = returnEntityType; +} + +// Set the return type of the public GetSync() or PostAsync() call. +var methodReturnType = string.Empty; +if (sendAsyncReturnType == null) +{ + methodReturnType = "System.Threading.Tasks.Task"; +} +else +{ + if (isCollection) + { + var collectionPage = "I" + entityName + methodName + "CollectionPage"; + methodReturnType = "System.Threading.Tasks.Task<" + collectionPage + ">"; + } + else + { + var returnParameter = sendAsyncReturnType == "ODataMethodIntResponse" || + sendAsyncReturnType == "ODataMethodBooleanResponse" || + sendAsyncReturnType == "ODataMethodLongResponse" ? returnEntityType + "?" + : returnEntityType; + methodReturnType = "System.Threading.Tasks.Task<" + returnParameter + ">"; + } +} string methodOverloadReturnType = methodReturnType; -if (isCollection) +if (isCollection || isPrimitiveReturnType) { methodReturnType = string.Concat("async ", methodReturnType); } @@ -178,9 +229,19 @@ namespace <#=@namespace#> } else if (!string.IsNullOrEmpty(sendAsyncReturnType)) { + if (isPrimitiveReturnType) + { +#> + var response = await this.SendAsync<<#=sendAsyncReturnType#>>(<#=methodParameter#>, cancellationToken); + return response.Value; +<# + } + else + { #> return this.SendAsync<<#=sendAsyncReturnType#>>(<#=methodParameter#>, cancellationToken); <# + } } else { @@ -278,9 +339,19 @@ namespace <#=@namespace#> } else if (!string.IsNullOrEmpty(sendAsyncReturnType)) { + if (isPrimitiveReturnType) + { +#> + var response = await this.SendAsync<<#=sendAsyncReturnType#>>(null, cancellationToken); + return response.Value; +<# + } + else + { #> return this.SendAsync<<#=sendAsyncReturnType#>>(null, cancellationToken); <# + } } else { diff --git a/test/Typewriter.Test/Given_a_valid_metadata_file_to_Typewriter.cs b/test/Typewriter.Test/Given_a_valid_metadata_file_to_Typewriter.cs index fe808712f..ba2ebc103 100644 --- a/test/Typewriter.Test/Given_a_valid_metadata_file_to_Typewriter.cs +++ b/test/Typewriter.Test/Given_a_valid_metadata_file_to_Typewriter.cs @@ -555,5 +555,42 @@ public void It_transforms_metadata() Assert.IsTrue(hasContainsTargetBeenSet, $"The expected ContainsTarget attribute wasn't set in the transformed cleaned metadata."); Assert.IsFalse(hasCapabilityAnnotations, $"The expected capability annotations weren't removed in the transformed cleaned metadata."); } + + [Test, RunInApplicationDomain] + [TestCase("TestType2FunctionMethodWithStringRequest.cs", "var response = await this.SendAsync(null, cancellationToken);")] + [TestCase("TestType2FunctionMethodWithBooleanRequest.cs", "var response = await this.SendAsync(null, cancellationToken);")] + [TestCase("TestType2FunctionMethodWithInt32Request.cs", "var response = await this.SendAsync(null, cancellationToken);")] + [TestCase("TestType3ActionMethodWithInt64Request.cs", "var response = await this.SendAsync(null, cancellationToken);")] + public void It_creates_method_request_with_OData_return_type(string outputFileName, string testParameter) + { + const string outputDirectory = "output"; + + Options optionsCSharp = new Options() + { + Output = outputDirectory, + Language = "CSharp", + GenerationMode = GenerationMode.Files + }; + + Generator.GenerateFiles(testMetadata, optionsCSharp); + + FileInfo fileInfo = new FileInfo(outputDirectory + generatedOutputUrl + @"\Requests\" + outputFileName); + Assert.IsTrue(fileInfo.Exists, $"Expected: {fileInfo.FullName}. File was not found."); + + IEnumerable lines = File.ReadLines(fileInfo.FullName); + bool hasTestParameter = false; + + foreach (var line in lines) + { + // We only need to check once. + if (line.Contains(testParameter)) + { + hasTestParameter = true; + break; + } + } + + Assert.IsTrue(hasTestParameter, $"The expected test token string, '{testParameter}', was not set in the generated test file. We didn't properly generate the SendAsync method."); + } } } diff --git a/test/Typewriter.Test/Resources/dirtyMetadata.xml b/test/Typewriter.Test/Resources/dirtyMetadata.xml index f3a52404c..7ba1deed7 100644 --- a/test/Typewriter.Test/Resources/dirtyMetadata.xml +++ b/test/Typewriter.Test/Resources/dirtyMetadata.xml @@ -92,6 +92,23 @@ + + + + + + + + + + + + + + + + +