diff --git a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs index 25674745..7c7578af 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs @@ -64,7 +64,7 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions } MethodDeclarationSyntax azureFunction = matchingMethods.First(); - var newParam = this.Parameters.bindingType == BindingType.input ? this.GenerateInputBinding() : this.GenerateOutputBinding(); + var newParam = this.Parameters.bindingType == BindingType.input ? this.GenerateInputBinding() : this.GenerateOutputBinding(azureFunction); // Generate updated method with the new parameter // normalizewhitespace gets rid of any newline whitespace in the leading trivia, so we add that back @@ -128,8 +128,12 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions /// /// Generates a parameter for the sql output binding that looks like /// [Sql("[dbo].[table1]", ConnectionStringSetting = "SqlConnectionString")] out Object output + /// if the Azure Function method is not async and + /// [Sql("[dbo].[table1]", ConnectionStringSetting = "SqlConnectionString")] IAsyncCollector output + /// if the Azure Function method is async. + /// Azure Function method. /// - private ParameterSyntax GenerateOutputBinding() + private ParameterSyntax GenerateOutputBinding(MethodDeclarationSyntax azureFunction) { // Create arguments for the Sql Output Binding attribute var argumentList = SyntaxFactory.AttributeArgumentList(); @@ -140,9 +144,22 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions attributesList = attributesList.Add(SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Sql")).WithArgumentList(argumentList)))); var syntaxTokenList = new SyntaxTokenList(); - syntaxTokenList = syntaxTokenList.Add(SyntaxFactory.Token(SyntaxKind.OutKeyword)); + TypeSyntax typeSyntax; - ParameterSyntax newParam = SyntaxFactory.Parameter(attributesList, syntaxTokenList, SyntaxFactory.ParseTypeName(typeof(Object).Name), SyntaxFactory.Identifier("output"), null); + // Check if Azure Function method is async + IEnumerable asyncModifier = azureFunction.Modifiers.Where(m => m.ToString() == "async"); + bool asyncMethod = asyncModifier.Count() > 0; + if (asyncMethod) + { + typeSyntax = SyntaxFactory.ParseTypeName("IAsyncCollector"); + + } else + { + syntaxTokenList = syntaxTokenList.Add(SyntaxFactory.Token(SyntaxKind.OutKeyword)); + typeSyntax = SyntaxFactory.ParseTypeName(typeof(Object).Name); + } + + ParameterSyntax newParam = SyntaxFactory.Parameter(attributesList, syntaxTokenList, typeSyntax, SyntaxFactory.Identifier("output"), null); return newParam; } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsInputBinding.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsInputBinding.cs index 4cc775b1..186f0cf0 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsInputBinding.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsInputBinding.cs @@ -55,5 +55,20 @@ namespace Company.Namespace // Spec Defines: HTTP 400 throw new NotImplementedException(); } + + /// Lets a user post new artists. + /// The Artists to use. + /// Raw HTTP Request. + /// The cancellation token provided on Function shutdown. + /// is null. + [FunctionName("NewArtists_post")] + public async IActionResult NewArtists([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req) + { + _logger.LogInformation("HTTP trigger function processed a request."); + // TODO: Handle Documented Responses. + // Spec Defines: HTTP 200 + // Spec Defines: HTTP 400 + throw new NotImplementedException(); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNoBindings.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNoBindings.cs index 006243b6..3256ba5d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNoBindings.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNoBindings.cs @@ -54,5 +54,20 @@ namespace Company.Namespace // Spec Defines: HTTP 400 throw new NotImplementedException(); } + + /// Lets a user post new artists. + /// The Artists to use. + /// Raw HTTP Request. + /// The cancellation token provided on Function shutdown. + /// is null. + [FunctionName("NewArtists_post")] + public async IActionResult NewArtists([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req) + { + _logger.LogInformation("HTTP trigger function processed a request."); + // TODO: Handle Documented Responses. + // Spec Defines: HTTP 200 + // Spec Defines: HTTP 400 + throw new NotImplementedException(); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBinding.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBinding.cs index aa449dd5..ddff241a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBinding.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBinding.cs @@ -54,5 +54,20 @@ namespace Company.Namespace // Spec Defines: HTTP 400 throw new NotImplementedException(); } + + /// Lets a user post new artists. + /// The Artists to use. + /// Raw HTTP Request. + /// The cancellation token provided on Function shutdown. + /// is null. + [FunctionName("NewArtists_post")] + public async IActionResult NewArtists([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req) + { + _logger.LogInformation("HTTP trigger function processed a request."); + // TODO: Handle Documented Responses. + // Spec Defines: HTTP 200 + // Spec Defines: HTTP 400 + throw new NotImplementedException(); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBindingAsync.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBindingAsync.cs new file mode 100644 index 00000000..9489fe6d --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBindingAsync.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Company.Namespace.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; + +namespace Company.Namespace +{ + public class ArtistsApi + { + private ILogger _logger; + + /// Initializes a new instance of ArtistsApi. + /// Class logger. + /// is null. + public ArtistsApi(ILogger logger) + { + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + _logger = logger; + } + + /// Returns a list of artists. + /// Raw HTTP Request. + /// The cancellation token provided on Function shutdown. + [FunctionName("GetArtists_get")] + public IActionResult GetArtists([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "artists")] HttpRequest req) + { + _logger.LogInformation("HTTP trigger function processed a request."); + // TODO: Handle Documented Responses. + // Spec Defines: HTTP 200 + // Spec Defines: HTTP 400 + throw new NotImplementedException(); + } + + /// Lets a user post a new artist. + /// The Artist to use. + /// Raw HTTP Request. + /// The cancellation token provided on Function shutdown. + /// is null. + [FunctionName("NewArtist_post")] + public IActionResult NewArtist([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req) + { + _logger.LogInformation("HTTP trigger function processed a request."); + // TODO: Handle Documented Responses. + // Spec Defines: HTTP 200 + // Spec Defines: HTTP 400 + throw new NotImplementedException(); + } + + /// Lets a user post new artists. + /// The Artists to use. + /// Raw HTTP Request. + /// The cancellation token provided on Function shutdown. + /// is null. + [FunctionName("NewArtists_post")] + public async IActionResult NewArtists([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req, [Sql("[dbo].[table1]", ConnectionStringSetting = "SqlConnectionString")] IAsyncCollector output) + { + _logger.LogInformation("HTTP trigger function processed a request."); + // TODO: Handle Documented Responses. + // Spec Defines: HTTP 200 + // Spec Defines: HTTP 400 + throw new NotImplementedException(); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs index d81d5c9a..0ed86460 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs @@ -76,6 +76,37 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions Assert.AreEqual(expectedFileText, actualFileText); } + /// + /// Verify output binding gets added for an async method + /// + [Test] + public void AddSqlOutputBindingAsync() + { + // copy the original file because the output binding will be inserted into the file + string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.cs"); + string testFile = Path.Join(Path.GetTempPath(), $"InsertSqlOutputBindingAsync-{DateTime.Now.ToString("yyyy - dd - MM--HH - mm - ss")}.cs"); + File.Copy(originalFile, testFile, true); + + AddSqlBindingParams parameters = new AddSqlBindingParams + { + bindingType = BindingType.output, + filePath = testFile, + functionName = "NewArtists_post", + objectName = "[dbo].[table1]", + connectionStringSetting = "SqlConnectionString" + }; + + AddSqlBindingOperation operation = new AddSqlBindingOperation(parameters); + ResultStatus result = operation.AddBinding(); + + Assert.True(result.Success); + Assert.IsNull(result.ErrorMessage); + + string expectedFileText = File.ReadAllText(Path.Join(testAzureFunctionsFolder, "AzureFunctionsOutputBindingAsync.cs")); + string actualFileText = File.ReadAllText(testFile); + Assert.AreEqual(expectedFileText, actualFileText); + } + /// /// Verify what happens when specified azure function isn't found /// @@ -150,9 +181,10 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions Assert.True(result.Success); Assert.Null(result.ErrorMessage); - Assert.AreEqual(2, result.azureFunctions.Length); + Assert.AreEqual(3, result.azureFunctions.Length); Assert.AreEqual(result.azureFunctions[0], "GetArtists_get"); Assert.AreEqual(result.azureFunctions[1], "NewArtist_post"); + Assert.AreEqual(result.azureFunctions[2], "NewArtists_post"); } /// diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj index c742f296..3bf44cd7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj @@ -44,6 +44,7 @@ +